diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 8d83633fe331..fb48223c408b 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -338,10 +338,20 @@ def _create_category_filter(file_type): class ObservationToSecurityQueryMapper(object): """Class to simplify the process of mapping observation data to query objects.""" + # Exfiltration consts _ENDPOINT_TYPE = "FedEndpointExfiltration" _CLOUD_TYPE = "FedCloudSharePermissions" + + # Query consts _PUBLIC_SEARCHABLE = "PublicSearchableShare" _PUBLIC_LINK = "PublicLinkShare" + _OUTSIDE_TRUSTED_DOMAINS = "SharedOutsideTrustedDomain" + + exposure_type_map = { + "PublicSearchableShare": ExposureType.IS_PUBLIC, + "PublicLinkShare": ExposureType.SHARED_VIA_LINK, + "SharedOutsideTrustedDomain": "OutsideTrustedDomains" + } def __init__(self, observation, actor): self._obs = observation @@ -390,19 +400,26 @@ def _create_search_args(self): return filters + @logger def _create_exposure_filters(self, exposure_types): """Determine exposure types based on alert type""" - + exp_types = [] if self._is_cloud_exfiltration: - exp_types = [] - if self._PUBLIC_SEARCHABLE in exposure_types: - exp_types.append(ExposureType.IS_PUBLIC) - if self._PUBLIC_LINK in exposure_types: - exp_types.append(ExposureType.SHARED_VIA_LINK) - return [ExposureType.is_in(exp_types)] + for t in exposure_types: + exp_type = self.exposure_type_map.get(t) + if exp_type: + exp_types.append(exp_type) + else: + LOG("Received unsupported exposure type {0}.".format(t)) + if exp_types: + return [ExposureType.is_in(exp_types)] + else: + # If not given a support exposure type, search for all unsupported exposure types + supported_exp_types = list(self.exposure_type_map.values()) + return [ExposureType.not_in(supported_exp_types)] elif self._is_endpoint_exfiltration: return [ - EventType.is_in(["CREATED", "MODIFIED", "READ_BY_APP"]), + EventType.is_in([EventType.CREATED, EventType.MODIFIED, EventType.READ_BY_APP]), ExposureType.is_in(exposure_types), ] return [] @@ -411,7 +428,8 @@ def _create_file_category_filters(self): """Determine if file categorization is significant""" observed_file_categories = self._observation_data["fileCategories"] categories = [c["category"].upper() for c in observed_file_categories if c["isSignificant"]] - return FileCategory.is_in(categories) + if categories: + return FileCategory.is_in(categories) def map_observation_to_security_query(observation, actor): diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index e2d7dba7ce58..e30512bac844 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -395,79 +395,188 @@ }""" MOCK_ALERT_DETAILS_RESPONSE = """{ - "type$": "ALERT_DETAILS_RESPONSE", - "alerts": [ - {"type$": "ALERT_DETAILS", - "tenantId": "1d71796f-af5b-4231-9d8e-df6434da4663", - "type": "FED_ENDPOINT_EXFILTRATION", - "name": "Departing Employee Alert", - "description": "Cortex XSOAR is cool.", - "actor": "user1@example.com", - "actorId": "912098363086307495", - "target": "N/A", - "severity": "HIGH", - "ruleId": "4576576e-13cb-4f88-be3a-ee77739de649", - "ruleSource": "Alerting", - "id": "36fb8ca5-0533-4d25-9763-e09d35d60610", - "createdAt": "2019-10-02T17:02:23.5867670Z", - "state": "OPEN", - "observations": [ - { - "type$": "OBSERVATION", - "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", - "observedAt": "2020-05-28T12:50:00.0000000Z", - "type": "FedEndpointExfiltration", - "data": { - "type$": "OBSERVED_ENDPOINT_ACTIVITY", - "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", - "sources": ["Endpoint"], - "exposureTypes": ["ApplicationRead"], - "firstActivityAt": "2020-05-28T12:50:00.0000000Z", - "lastActivityAt": "2020-05-28T12:50:00.0000000Z", - "fileCount": 3, - "totalFileSize": 533846, - "fileCategories": [ - { - "type$": "OBSERVED_FILE_CATEGORY", - "category": "Image", - "fileCount": 3, - "totalFileSize": 533846, - "isSignificant": true - } - ], - "files": [ - { - "type$": "OBSERVED_FILE", - "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", - "path": "C:/Users/QA/Downloads/", - "name": "Customers.jpg", - "category": "Image", - "size": 265122 - }, - { - "type$": "OBSERVED_FILE", - "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_6", - "path": "C:/Users/QA/Downloads/", - "name": "data.png", - "category": "Image", - "size": 129129 - }, - { - "type$": "OBSERVED_FILE", - "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_7", - "path": "C:/Users/QA/Downloads/", - "name": "company_secrets.ps", - "category": "Image", - "size": 139595 - } - ], - "syncToServices": [], - "sendingIpAddresses": ["127.0.0.1"] - } - } + "type$": "ALERT_DETAILS_RESPONSE", + "alerts": [ + { + "type$": "ALERT_DETAILS", + "tenantId": "1d71796f-af5b-4231-9d8e-df6434da4663", + "type": "FED_ENDPOINT_EXFILTRATION", + "name": "Departing Employee Alert", + "description": "Cortex XSOAR is cool.", + "actor": "user1@example.com", + "actorId": "912098363086307495", + "target": "N/A", + "severity": "HIGH", + "ruleId": "4576576e-13cb-4f88-be3a-ee77739de649", + "ruleSource": "Alerting", + "id": "36fb8ca5-0533-4d25-9763-e09d35d60610", + "createdAt": "2019-10-02T17:02:23.5867670Z", + "state": "OPEN", + "observations": [ + { + "type$": "OBSERVATION", + "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", + "observedAt": "2020-05-28T12:50:00.0000000Z", + "type": "FedEndpointExfiltration", + "data": { + "type$": "OBSERVED_ENDPOINT_ACTIVITY", + "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", + "sources": [ + "Endpoint" + ], + "exposureTypes": [ + "ApplicationRead" + ], + "firstActivityAt": "2020-05-28T12:50:00.0000000Z", + "lastActivityAt": "2020-05-28T12:50:00.0000000Z", + "fileCount": 3, + "totalFileSize": 533846, + "fileCategories": [ + { + "type$": "OBSERVED_FILE_CATEGORY", + "category": "Image", + "fileCount": 3, + "totalFileSize": 533846, + "isSignificant": true + } + ], + "files": [ + { + "type$": "OBSERVED_FILE", + "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", + "path": "C:/Users/QA/Downloads/", + "name": "Customers.jpg", + "category": "Image", + "size": 265122 + }, + { + "type$": "OBSERVED_FILE", + "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_6", + "path": "C:/Users/QA/Downloads/", + "name": "data.png", + "category": "Image", + "size": 129129 + }, + { + "type$": "OBSERVED_FILE", + "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_7", + "path": "C:/Users/QA/Downloads/", + "name": "company_secrets.ps", + "category": "Image", + "size": 139595 + } + ], + "syncToServices": [], + "sendingIpAddresses": [ + "127.0.0.1" ] + } + }, + { + "type$": "OBSERVATION", + "id": "7f4d125d-c7ca-4264-83fe-fa442bf270b6", + "observedAt": "2020-06-11T20:20:00.0000000Z", + "type": "FedCloudSharePermissions", + "data": { + "type$": "OBSERVED_CLOUD_SHARE_ACTIVITY", + "id": "7f4d125d-c7ca-4264-83fe-fa442bf270b6", + "sources": [ + "GoogleDrive" + ], + "exposureTypes": [ + "SharedOutsideTrustedDomain" + ], + "firstActivityAt": "2020-06-11T20:20:00.0000000Z", + "lastActivityAt": "2020-06-11T20:25:00.0000000Z", + "fileCount": 1, + "totalFileSize": 182554405, + "fileCategories": [ + { + "type$": "OBSERVED_FILE_CATEGORY", + "category": "Archive", + "fileCount": 1, + "totalFileSize": 182554405, + "isSignificant": false + } + ], + "files": [ + { + "type$": "OBSERVED_FILE", + "eventId": "14FnN9-YOhVUO_Tv8Mu-hEgevc2K4l07l_5_9e633ffd-9329-4cf4-8645-27a23b83ebc0", + "name": "Code42CrashPlan_8.0.0_1525200006800_778_Mac.dmg", + "category": "Archive", + "size": 182554405 + } + ], + "outsideTrustedDomainsEmails": [ + "user1@example.com" + ], + "outsideTrustedDomainsEmailsCount": 1, + "outsideTrustedDomainsCounts": [ + { + "type$": "OBSERVED_DOMAIN_INFO", + "domain": "gmail.com", + "count": 1 + } + ], + "outsideTrustedDomainsTotalDomainCount": 1, + "outsideTrustedDomainsTotalDomainCountTruncated": false + } + }, + { + "type$": "OBSERVATION", + "id": "7f4d125d-c7ca-4264-83fe-fa442bf270b6", + "observedAt": "2020-06-11T20:20:00.0000000Z", + "type": "FedCloudSharePermissions", + "data": { + "type$": "OBSERVED_CLOUD_SHARE_ACTIVITY", + "id": "7f4d125d-c7ca-4264-83fe-fa442bf270b6", + "sources": [ + "GoogleDrive" + ], + "exposureTypes": [ + "UnknownExposureTypeThatWeDontSupportYet" + ], + "firstActivityAt": "2020-06-11T20:20:00.0000000Z", + "lastActivityAt": "2020-06-11T20:25:00.0000000Z", + "fileCount": 1, + "totalFileSize": 182554405, + "fileCategories": [ + { + "type$": "OBSERVED_FILE_CATEGORY", + "category": "Archive", + "fileCount": 1, + "totalFileSize": 182554405, + "isSignificant": false + } + ], + "files": [ + { + "type$": "OBSERVED_FILE", + "eventId": "14FnN9-YOhVUO_Tv8Mu-hEgevc2K4l07l_5_9e633ffd-9329-4cf4-8645-27a23b83ebc0", + "name": "Code42CrashPlan_8.0.0_1525200006800_778_Mac.dmg", + "category": "Archive", + "size": 182554405 + } + ], + "outsideTrustedDomainsEmails": [ + "user1@example.com" + ], + "outsideTrustedDomainsEmailsCount": 1, + "outsideTrustedDomainsCounts": [ + { + "type$": "OBSERVED_DOMAIN_INFO", + "domain": "gmail.com", + "count": 1 + } + ], + "outsideTrustedDomainsTotalDomainCount": 1, + "outsideTrustedDomainsTotalDomainCountTruncated": false + } } - ] + ] + } + ] }""" MOCK_CODE42_ALERT_CONTEXT = [ @@ -590,7 +699,7 @@ "groups": [ { "filterClause": "AND", - "filters": [{"operator": "IS", "term": "actor", "value": "user2@example.com"}], + "filters": [{"operator": "IS", "term": "actor", "value": "user1@example.com"}], }, { "filterClause": "AND", @@ -598,7 +707,7 @@ { "operator": "ON_OR_AFTER", "term": "eventTimestamp", - "value": "2019-10-02T16:50:00.000Z", + "value": "2020-06-11T20:20:00.000Z", } ], }, @@ -608,15 +717,55 @@ { "operator": "ON_OR_BEFORE", "term": "eventTimestamp", - "value": "2019-10-02T16:55:00.000Z", + "value": "2020-06-11T20:25:00.000Z", } ], }, { - "filterClause": "OR", + "filterClause": "AND", + "filters": [ + {"operator": "IS", "term": "exposure", "value": "OutsideTrustedDomains"} + ], + }, + ], + "pgNum": 1, + "pgSize": 10000, + "srtDir": "asc", + "srtKey": "eventId", + }, + { + "groupClause": "AND", + "groups": [ + { + "filterClause": "AND", + "filters": [{"operator": "IS", "term": "actor", "value": "user1@example.com"}], + }, + { + "filterClause": "AND", + "filters": [ + { + "operator": "ON_OR_AFTER", + "term": "eventTimestamp", + "value": "2020-06-11T20:20:00.000Z", + } + ], + }, + { + "filterClause": "AND", + "filters": [ + { + "operator": "ON_OR_BEFORE", + "term": "eventTimestamp", + "value": "2020-06-11T20:25:00.000Z", + } + ], + }, + { + "filterClause": "AND", "filters": [ - {"operator": "IS", "term": "exposure", "value": "IsPublic"}, - {"operator": "IS", "term": "exposure", "value": "SharedViaLink"}, + {"operator": "IS_NOT", "term": "exposure", "value": "IsPublic"}, + {"operator": "IS_NOT", "term": "exposure", "value": "SharedViaLink"}, + {"operator": "IS_NOT", "term": "exposure", "value": "OutsideTrustedDomains"}, ], }, ], @@ -926,7 +1075,9 @@ def get_empty_detectionlist_response(mocker, base_text): def test_client_when_no_alert_found_raises_exception(code42_sdk_mock): - code42_sdk_mock.alerts.get_details.return_value = """{'type$': 'ALERT_DETAILS_RESPONSE', 'alerts': []}""" + code42_sdk_mock.alerts.get_details.return_value = ( + """{'type$': 'ALERT_DETAILS_RESPONSE', 'alerts': []}""" + ) client = create_client(code42_sdk_mock) with pytest.raises(Exception): client.get_alert_details("mock-id") @@ -948,14 +1099,15 @@ def test_build_query_payload(): def test_map_observation_to_security_query(): response = json.loads(MOCK_ALERT_DETAILS_RESPONSE) - alerts = response["alerts"] - for i in range(0, len(alerts)): - observation = alerts[i]["observations"][0] - actor = alerts[i]["actor"] - query = map_observation_to_security_query(observation, actor) - assert query.sort_key == MOCK_OBSERVATION_QUERIES[i]["srtKey"] - assert query.page_number == MOCK_OBSERVATION_QUERIES[i]["pgNum"] - assert json.loads(str(query)) == MOCK_OBSERVATION_QUERIES[i] + alert = response["alerts"][0] + actor = alert["actor"] + observations = alert["observations"] + actual_queries = [ + json.loads(str(map_observation_to_security_query(o, actor))) for o in observations + ] + assert actual_queries[0] == MOCK_OBSERVATION_QUERIES[0] + assert actual_queries[1] == MOCK_OBSERVATION_QUERIES[1] + assert actual_queries[2] == MOCK_OBSERVATION_QUERIES[2] def test_map_to_code42_event_context(): @@ -1096,6 +1248,29 @@ def test_highriskemployee_remove_command(code42_sdk_mock): code42_sdk_mock.detectionlists.high_risk_employee.remove.assert_called_once_with(expected) +def test_fetch_when_no_significant_file_categories_ignores_filter( + code42_fetch_incidents_mock, mocker +): + response_text = MOCK_ALERT_DETAILS_RESPONSE.replace( + '"isSignificant": true', '"isSignificant": false' + ) + alert_details_response = create_mock_code42_sdk_response(mocker, response_text) + code42_fetch_incidents_mock.alerts.get_details.return_value = alert_details_response + client = create_client(code42_fetch_incidents_mock) + _, _, _ = fetch_incidents( + client=client, + last_run={"last_fetch": None}, + first_fetch_time=MOCK_FETCH_TIME, + event_severity_filter=None, + fetch_limit=10, + include_files=True, + integration_context=None, + ) + actual_query = str(code42_fetch_incidents_mock.securitydata.search_file_events.call_args[0][0]) + assert "fileCategory" not in actual_query + assert "IMAGE" not in actual_query + + def test_highriskemployee_get_all_command(code42_high_risk_employee_mock): client = create_client(code42_high_risk_employee_mock) _, _, res = highriskemployee_get_all_command(client, {}) diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 1bf98b61977e..0bd326f1a27a 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -410,10 +410,20 @@ script: class ObservationToSecurityQueryMapper(object): """Class to simplify the process of mapping observation data to query objects.""" + # Exfiltration consts _ENDPOINT_TYPE = "FedEndpointExfiltration" _CLOUD_TYPE = "FedCloudSharePermissions" + + # Query consts _PUBLIC_SEARCHABLE = "PublicSearchableShare" _PUBLIC_LINK = "PublicLinkShare" + _OUTSIDE_TRUSTED_DOMAINS = "SharedOutsideTrustedDomain" + + exposure_type_map = { + "PublicSearchableShare": ExposureType.IS_PUBLIC, + "PublicLinkShare": ExposureType.SHARED_VIA_LINK, + "SharedOutsideTrustedDomain": "OutsideTrustedDomains" + } def __init__(self, observation, actor): self._obs = observation @@ -462,19 +472,26 @@ script: return filters + @logger def _create_exposure_filters(self, exposure_types): """Determine exposure types based on alert type""" - + exp_types = [] if self._is_cloud_exfiltration: - exp_types = [] - if self._PUBLIC_SEARCHABLE in exposure_types: - exp_types.append(ExposureType.IS_PUBLIC) - if self._PUBLIC_LINK in exposure_types: - exp_types.append(ExposureType.SHARED_VIA_LINK) - return [ExposureType.is_in(exp_types)] + for t in exposure_types: + exp_type = self.exposure_type_map.get(t) + if exp_type: + exp_types.append(exp_type) + else: + LOG("Received unsupported exposure type {0}.".format(t)) + if exp_types: + return [ExposureType.is_in(exp_types)] + else: + # If not given a support exposure type, search for all unsupported exposure types + supported_exp_types = list(self.exposure_type_map.values()) + return [ExposureType.not_in(supported_exp_types)] elif self._is_endpoint_exfiltration: return [ - EventType.is_in(["CREATED", "MODIFIED", "READ_BY_APP"]), + EventType.is_in([EventType.CREATED, EventType.MODIFIED, EventType.READ_BY_APP]), ExposureType.is_in(exposure_types), ] return [] @@ -483,7 +500,8 @@ script: """Determine if file categorization is significant""" observed_file_categories = self._observation_data["fileCategories"] categories = [c["category"].upper() for c in observed_file_categories if c["isSignificant"]] - return FileCategory.is_in(categories) + if categories: + return FileCategory.is_in(categories) def map_observation_to_security_query(observation, actor):