コード例 #1
0
    def _handle_update_malop_status(self, param):
        self.save_progress("In _handle_update_malop_status function")
        self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        malop_id = self._get_string_param(param.get('malop_id'))

        phantom_status = param.get('status')
        cybereason_status = PHANTOM_TO_CYBEREASON_STATUS.get(phantom_status)
        if not cybereason_status:
            self.save_progress("Invalid status selected")
            return action_result.set_status(phantom.APP_ERROR, "Invalid status. Please provide a valid value in the 'status' action parameter")

        try:
            cr_session = CybereasonSession(self).get_session()

            url = "{0}/rest/crimes/status".format(self._base_url)
            self.save_progress(url)
            query = json.dumps({malop_id: cybereason_status})
            res = cr_session.post(url, data=query, headers=self._headers)

            if res.status_code < 200 or res.status_code >= 399:
                self._process_response(res, action_result)
                return action_result.get_status()

        except Exception as e:
            err = self._get_error_message_from_exception(e)
            return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #2
0
    def _handle_unisolate_machine(self, param):
        self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        malop_id = self._get_string_param(param.get('malop_id'))
        ret_val, sensor_ids = self._get_malop_sensor_ids(malop_id, action_result)
        if phantom.is_fail(ret_val):
            return action_result.get_status()

        try:
            cr_session = CybereasonSession(self).get_session()

            url = "{0}/rest/monitor/global/commands/un-isolate".format(self._base_url)
            self.save_progress(url)
            query = json.dumps({"pylumIds": sensor_ids, "malopId": malop_id})

            res = cr_session.post(url, data=query, headers=self._headers)

            if res.status_code < 200 or res.status_code >= 399:
                self._process_response(res, action_result)
                return action_result.get_status()

        except Exception as e:
            err = self._get_error_message_from_exception(e)
            return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #3
0
    def _handle_set_reputation(self, param):
        self.save_progress("In _handle_set_reputation function")
        self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        reputation_item = self._get_string_param(param.get('reputation_item_hash'))
        custom_reputation = param.get('custom_reputation')
        if custom_reputation not in CUSTOM_REPUTATION_LIST:
            return action_result.set_status(phantom.APP_ERROR, "Please provide a valid value for the 'custom_reputation' action parameter")

        try:
            cr_session = CybereasonSession(self).get_session()

            url = "{0}/rest/classification/update".format(self._base_url)
            self.save_progress(url)
            if custom_reputation == 'remove':
                reputation = json.dumps([{"keys": [reputation_item], "maliciousType": None, "prevent": False, "remove": True}])
            else:
                reputation = json.dumps([{"keys": [reputation_item], "maliciousType": custom_reputation, "prevent": False, "remove": False}])

            res = cr_session.post(url, data=reputation, headers=self._headers)
            if res.status_code < 200 or res.status_code >= 399:
                self._process_response(res, action_result)
                return action_result.get_status()

            self.save_progress("{0}ed...".format(custom_reputation))
        except Exception as e:
            err = self._get_error_message_from_exception(e)
            return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #4
0
    def _handle_add_malop_comment(self, param):
        self.save_progress("In _handle_add_malop_comment function")
        self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        malop_id = self._get_string_param(param.get('malop_id'))
        self.save_progress("MALOP ID  :{0}".format(malop_id))

        comment = param.get('comment', "")
        self.save_progress("COMMENT  :{0}".format(comment))

        try:
            cr_session = CybereasonSession(self).get_session()

            endpoint_url = "/rest/crimes/comment/"
            url = "{0}{1}{2}".format(self._base_url, endpoint_url, str(malop_id))
            self.save_progress(url)

            res = cr_session.post(url, data=comment.encode('utf-8'), headers=self._headers)

            if res.status_code < 200 or res.status_code >= 399:
                self._process_response(res, action_result)
                return action_result.get_status()
        except requests.exceptions.ConnectionError:
            err = "Error Details: Connection refused from the server"
            return action_result.set_status(phantom.APP_ERROR, err)
        except Exception as e:
            err = self._get_error_message_from_exception(e)
            return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #5
0
    def _handle_delete_registry_key(self, param):
        self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        malop_id = self._get_string_param(param['malop_id'])
        machine_name = param["machine_name"].lower()

        # Get the remediation target
        cr_session = CybereasonSession(self).get_session()
        try:
            ret_val, remediate_body = self._get_delete_registry_key_body(cr_session, malop_id, machine_name, action_result)
            if phantom.is_fail(ret_val):
                return action_result.get_status()

            # Make the call to remediate the action
            res = cr_session.post("{0}/rest/remediate".format(self._base_url), json=remediate_body, headers=self._headers)

            if res.status_code < 200 or res.status_code >= 399:
                self._process_response(res, action_result)
                return action_result.get_status()

            result = res.json()
            action_result.add_data({
                "remediation_id": result["remediationId"],
                "initiating_user": result["initiatingUser"]
            })
        except Exception as e:
            err = self._get_error_message_from_exception(e)
            return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #6
0
    def _handle_test_connectivity(self, param):
        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        # Set up a session by logging in to the Cybereason console.
        cr_session = CybereasonSession(self)
        cookies = cr_session.get_session_cookies()
        if cookies.get("JSESSIONID"):
            # We have a session id cookie, so the authentication succeeded
            self.save_progress('Successfully connected to the Cybereason console and verified session cookie')
            return action_result.set_status(phantom.APP_SUCCESS, 'Successfully connected to the Cybereason console and verified session cookie')
        else:
            return action_result.set_status(phantom.APP_ERROR, 'Connectivity failed. Unable to get session cookie from Cybereason console')
コード例 #7
0
    def _handle_kill_process(self, param):
        self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        malop_id = self._get_string_param(param["malop_id"])
        machine_id = self._get_string_param(param["machine_id"])
        remediation_user = param["remediation_user"]
        process_id = self._get_string_param(param["process_id"])

        try:
            cr_session = CybereasonSession(self).get_session()
            url = "{0}/rest/remediate".format(self._base_url)
            query = {
                "malopId": malop_id,
                "initiatorUserName": remediation_user,
                "actionsByMachine": {
                    machine_id: [
                        {
                            "targetId": process_id,
                            "actionType": "KILL_PROCESS"
                        }
                    ]
                }
            }
            res = cr_session.post(url, json=query, headers=self._headers)

            if res.status_code < 200 or res.status_code >= 399:
                self._process_response(res, action_result)
                return action_result.get_status()

            result = res.json()
            if len(result["statusLog"]) > 0:
                action_result.add_data({
                    "remediation_id": result["remediationId"],
                    "remediation_status": result["statusLog"][0]["status"]
                })
        except Exception as e:
            err = self._get_error_message_from_exception(e)
            self.debug_print(err)
            self.debug_print(traceback.format_exc())
            return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #8
0
    def _handle_get_remediation_status(self, param):
        self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        malop_id = self._get_string_param(param["malop_id"])
        remediation_user = param["remediation_user"]
        remediation_id = param["remediation_id"]

        try:
            cr_session = CybereasonSession(self).get_session()
            url = "{0}/rest/remediate/progress/{1}/{2}/{3}".format(self._base_url, remediation_user, malop_id, remediation_id)
            res = cr_session.get(url)

            if res.status_code < 200 or res.status_code >= 399:
                self._process_response(res, action_result)
                return action_result.get_status()

            result = res.json()
            status_log_length = len(result["statusLog"])
            error_obj = result["statusLog"][status_log_length - 1]["error"]
            action_result.add_data({
                "remediation_status": result["statusLog"][status_log_length - 1]["status"],
                "remediation_message": error_obj.get("message", "Unknown error") if error_obj is not None else "No error message"
            })
        except requests.exceptions.ConnectionError:
            err = "Error Details: Connection refused from the server"
            return action_result.set_status(phantom.APP_ERROR, err)
        except Exception as e:
            err = self._get_error_message_from_exception(e)
            self.debug_print(err)
            self.debug_print(traceback.format_exc())
            return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #9
0
    def _handle_get_sensor_status(self, param):
        self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = self.add_action_result(ActionResult(dict(param)))

        malop_id = self._get_string_param(param.get('malop_id'))

        try:
            # Set up a session by logging in to the Cybereason console.
            cr_session = CybereasonSession(self).get_session()
            url = "{0}/rest/visualsearch/query/simple".format(self._base_url)
            post_data = {
                "queryPath": [
                    {
                        "requestedType": "MalopProcess",
                        "filters": [],
                        "guidList": [
                            malop_id
                        ],
                        "connectionFeature": {
                            "elementInstanceType": "MalopProcess",
                            "featureName": "suspects"
                        }
                    },
                    {
                        "requestedType": "Process",
                        "filters": [],
                        "connectionFeature": {
                            "elementInstanceType": "Process",
                            "featureName": "ownerMachine"
                        }
                    },
                    {
                        "requestedType": "Machine",
                        "filters": [],
                        "isResult": True
                    }
                ],
                "totalResultLimit": 1000,
                "perGroupLimit": 1200,
                "perFeatureLimit": 1200,
                "templateContext": "SPECIFIC",
                "queryTimeout": 30,
                "customFields": [
                    "isConnected",
                    "elementDisplayName"
                ]
            }
            res = cr_session.post(url=url, headers=self._headers, json=post_data)

            if res.status_code < 200 or res.status_code >= 399:
                self._process_response(res, action_result)
                return action_result.get_status()

            self.save_progress("Successfully fetched machine details from Cybereason console")
            machines_dict = res.json()["data"]["resultIdToElementDataMap"]

            for machine_id, machine_details in machines_dict.items():
                action_result.add_data({
                    "machine_id": machine_id,
                    "machine_name": machine_details["simpleValues"]["elementDisplayName"]["values"][0],
                    "status": "Online" if machine_details["simpleValues"]["isConnected"]["values"][0] == "true" else "Offline"
                })
        except Exception as e:
            err = self._get_error_message_from_exception(e)
            return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #10
0
    def do_poll(self, connector, param):
        action_result = connector.add_action_result(ActionResult(dict(param)))
        success = True
        try:
            # Declare data that will be lazy-loaded if required
            self.feature_translation = None
            config = connector.get_config()
            state = connector.get_state()
            current_time = datetime.datetime.now()
            is_first_poll = state.get("is_first_poll", True)

            ret_val, malop_historical_days = connector._validate_integer(
                action_result, config["malop_historical_days"],
                MALOP_HISTORICAL_DAYS_KEY)
            if phantom.is_fail(ret_val):
                return action_result.get_status()

            ret_val, malware_historical_days = connector._validate_integer(
                action_result, config["malware_historical_days"],
                MALWARE_HISTORICAL_DAYS_KEY)
            if phantom.is_fail(ret_val):
                return action_result.get_status()

            if is_first_poll:
                connector.save_progress(
                    "This is a first time poll. We will poll for malops from the last {days} days",
                    days=malop_historical_days)
                malop_start_time = current_time + datetime.timedelta(
                    days=-malop_historical_days)
                connector.save_progress(
                    "This is a first time poll. We will poll for malware from the last {days} days",
                    days=malware_historical_days)
                malware_millisec_since_last_poll = malware_historical_days * 60 * 60 * 24 * 1000
                state["is_first_poll"] = False
            else:
                last_poll_timestamp = datetime.datetime.fromtimestamp(
                    state["last_poll_timestamp"])
                malop_start_time = last_poll_timestamp
                malware_millisec_since_last_poll = round(
                    (current_time - last_poll_timestamp).total_seconds() *
                    1000)
            state["last_poll_timestamp"] = current_time.timestamp()
            connector.save_progress(
                "Getting malops between {start_time} and {current_time}",
                start_time=malop_start_time,
                current_time=current_time)
            connector.save_progress(
                "Getting malware for the last {msec} milliseconds",
                msec=malware_millisec_since_last_poll)

            # Initialize the session that will be used throughout the poller
            self.cr_session = CybereasonSession(connector).get_session()
            malop_start_time_microsec_timestamp = round(
                malop_start_time.timestamp() * 1000)
            # When called as a scheduled poll, max_container count comes as 4294967295 which causes a Cybereason API error.
            container_count = min(
                int(param.get(phantom.APP_JSON_CONTAINER_COUNT)), 5000)
            success = success & self._fetch_and_ingest_malops(
                connector, config, malop_start_time_microsec_timestamp,
                container_count)
            success = success & self._fetch_and_ingest_malwares(
                connector, config, malware_millisec_since_last_poll,
                container_count)
        except Exception as e:
            success = False
            err = connector._get_error_message_from_exception(e)
            connector.debug_print("Exception when polling")
            connector.debug_print(err)
            connector.debug_print(traceback.format_exc())
        finally:
            connector.save_state(state)

        if success:
            connector.save_progress(
                "Successfully completed polling for Malop and Malware events")
            return action_result.set_status(
                phantom.APP_SUCCESS,
                "Malop and Malware ingestion completed successfully")
        else:
            return action_result.set_status(
                phantom.APP_ERROR,
                "Error when polling for Malop and Malware. Please refer the logs for more details"
            )
コード例 #11
0
    def _handle_query_machine(self, connector, param):
        connector.save_progress("In action handler for: {0}".format(
            connector.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = connector.add_action_result(ActionResult(dict(param)))

        name = connector._get_string_param(param.get('name'))
        malops_dict = {}
        try:
            cr_session = CybereasonSession(connector).get_session()

            query = {
                "queryPath": [{
                    "requestedType":
                    "Machine",
                    "filters": [{
                        "facetName": "elementDisplayName",
                        "values": [name],
                        "filterType": "MatchesWildcard"
                    }],
                    "isResult":
                    True
                }],
                "totalResultLimit":
                1000,
                "perGroupLimit":
                100,
                "perFeatureLimit":
                100,
                "templateContext":
                "SPECIFIC",
                "queryTimeout":
                120000,
                "customFields": [
                    "osVersionType", "platformArchitecture", "uptime",
                    "isActiveProbeConnected", "lastSeenTimeStamp",
                    "timeStampSinceLastConnectionTime", "activeUsers",
                    "mountPoints", "processes", "services",
                    "elementDisplayName"
                ]
            }
            url = "{0}/rest/visualsearch/query/simple".format(
                connector._base_url)

            res = cr_session.post(url, json=query, headers=connector._headers)

            if res.status_code < 200 or res.status_code >= 399:
                connector._process_response(res, action_result)
                return action_result.get_status()

            malops_dict = res.json()["data"]["resultIdToElementDataMap"]
            for machine_id, machine_data in malops_dict.items():
                data = {
                    "machine_id":
                    machine_id,
                    "machine_name":
                    machine_data["simpleValues"]["elementDisplayName"]
                    ["values"][0]
                }
                self._add_simple_value_if_exists(data, "os_version",
                                                 machine_data, "osVersionType")
                self._add_simple_value_if_exists(data, "platform_architecture",
                                                 machine_data,
                                                 "platformArchitecture")
                self._add_simple_value_if_exists(data,
                                                 "is_connected_to_cybereason",
                                                 machine_data,
                                                 "isActiveProbeConnected")
                action_result.add_data(data)

            summary = action_result.update_summary({})
            summary['total_machines'] = len(malops_dict)

        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(err)
            connector.debug_print(traceback.format_exc())
            return action_result.set_status(phantom.APP_ERROR,
                                            "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #12
0
    def _handle_query_connections(self, connector, param):
        connector.save_progress("In action handler for: {0}".format(
            connector.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = connector.add_action_result(ActionResult(dict(param)))

        connection_name = connector._get_string_param(
            param.get('connection_name'))
        malops_dict = {}
        try:
            cr_session = CybereasonSession(connector).get_session()

            query = {
                "queryPath": [{
                    "requestedType":
                    "Connection",
                    "filters": [{
                        "facetName": "elementDisplayName",
                        "values": [connection_name],
                        "filterType": "MatchesWildcard"
                    }],
                    "isResult":
                    True
                }],
                "totalResultLimit":
                1000,
                "perGroupLimit":
                100,
                "perFeatureLimit":
                100,
                "templateContext":
                "SPECIFIC",
                "queryTimeout":
                120000,
                "customFields": [
                    "direction", "serverAddress", "serverPort", "portType",
                    "aggregatedReceivedBytesCount",
                    "aggregatedTransmittedBytesCount",
                    "remoteAddressCountryName", "accessedByMalwareEvidence",
                    "ownerMachine", "ownerProcess", "dnsQuery",
                    "calculatedCreationTime", "endTime", "elementDisplayName"
                ]
            }
            url = "{0}/rest/visualsearch/query/simple".format(
                connector._base_url)

            res = cr_session.post(url, json=query, headers=connector._headers)

            if res.status_code < 200 or res.status_code >= 399:
                connector._process_response(res, action_result)
                return action_result.get_status()

            malops_dict = res.json()["data"]["resultIdToElementDataMap"]
            for _, connection_data in malops_dict.items():
                # Name contains characters like ">" which will be escaped to "&gt;" when showing in the output table
                name = connection_data["simpleValues"]["elementDisplayName"][
                    "values"][0]
                name = name.replace('>', ' [to] ')
                name = name.replace('<', ' [from] ')
                data = {"element_name": name}
                self._add_simple_value_if_exists(data, "direction",
                                                 connection_data, "direction")
                self._add_simple_value_if_exists(data, "server_address",
                                                 connection_data,
                                                 "serverAddress")
                self._add_simple_value_if_exists(data, "server_port",
                                                 connection_data, "serverPort")
                self._add_simple_value_if_exists(data, "port_type",
                                                 connection_data, "portType")
                self._add_simple_value_if_exists(
                    data, "received_bytes", connection_data,
                    "aggregatedReceivedBytesCount")
                self._add_simple_value_if_exists(
                    data, "transmitted_bytes", connection_data,
                    "aggregatedTransmittedBytesCount")
                self._add_simple_value_if_exists(data, "remote_address",
                                                 connection_data,
                                                 "remoteAddressCountryName")

                self._add_element_value_if_exists(data, "owner_machine",
                                                  connection_data,
                                                  "ownerMachine", "name")
                self._add_element_value_if_exists(data, "owner_process",
                                                  connection_data,
                                                  "ownerProcess", "name")
                self._add_element_value_if_exists(data, "dns_query",
                                                  connection_data, "dnsQuery",
                                                  "name")
                action_result.add_data(data)

            summary = action_result.update_summary({})
            summary['total_results'] = len(malops_dict)
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(err)
            connector.debug_print(traceback.format_exc())
            return action_result.set_status(phantom.APP_ERROR,
                                            "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #13
0
    def _handle_query_domain(self, connector, param):
        connector.save_progress("In action handler for: {0}".format(
            connector.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = connector.add_action_result(ActionResult(dict(param)))

        domain_name = connector._get_string_param(param.get('domain_name'))
        malops_dict = {}
        try:
            cr_session = CybereasonSession(connector).get_session()

            query = {
                "queryPath": [{
                    "requestedType":
                    "DomainName",
                    "filters": [{
                        "facetName": "elementDisplayName",
                        "values": [domain_name],
                        "filterType": "MatchesWildcard"
                    }],
                    "isResult":
                    True
                }],
                "totalResultLimit":
                1000,
                "perGroupLimit":
                100,
                "perFeatureLimit":
                100,
                "templateContext":
                "SPECIFIC",
                "queryTimeout":
                120000,
                "customFields": [
                    "maliciousClassificationType", "isInternalDomain",
                    "everResolvedDomain", "everResolvedSecondLevelDomain",
                    "elementDisplayName"
                ]
            }
            url = "{0}/rest/visualsearch/query/simple".format(
                connector._base_url)

            res = cr_session.post(url, json=query, headers=connector._headers)

            if res.status_code < 200 or res.status_code >= 399:
                connector._process_response(res, action_result)
                return action_result.get_status()

            malops_dict = res.json()["data"]["resultIdToElementDataMap"]

            for _, domain_data in malops_dict.items():
                data = {
                    "element_name":
                    domain_data["simpleValues"]["elementDisplayName"]["values"]
                    [0]
                }
                self._add_simple_value_if_exists(
                    data, "malicious_classification_type", domain_data,
                    "maliciousClassificationType")
                self._add_simple_value_if_exists(data, "is_internal_domain",
                                                 domain_data,
                                                 "isInternalDomain")
                self._add_simple_value_if_exists(data, "was_ever_resolved",
                                                 domain_data,
                                                 "everResolvedDomain")
                self._add_simple_value_if_exists(
                    data, "was_ever_resolved_as_second_level_domain",
                    domain_data, "everResolvedSecondLevelDomain")
                action_result.add_data(data)

            summary = action_result.update_summary({})
            summary['total_results'] = len(malops_dict)
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(err)
            connector.debug_print(traceback.format_exc())
            return action_result.set_status(phantom.APP_ERROR,
                                            "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #14
0
    def _handle_query_files(self, connector, param):
        connector.save_progress("In action handler for: {0}".format(
            connector.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = connector.add_action_result(ActionResult(dict(param)))

        file_name = connector._get_string_param(param.get('file_name'))
        malops_dict = {}
        try:
            cr_session = CybereasonSession(connector).get_session()

            query = {
                "queryPath": [{
                    "requestedType":
                    "File",
                    "filters": [{
                        "facetName": "elementDisplayName",
                        "values": [file_name],
                        "filterType": "ContainsIgnoreCase"
                    }],
                    "isResult":
                    True
                }],
                "totalResultLimit":
                1000,
                "perGroupLimit":
                100,
                "perFeatureLimit":
                100,
                "templateContext":
                "SPECIFIC",
                "queryTimeout":
                120000,
                "customFields": [
                    "ownerMachine", "avRemediationStatus", "isSigned",
                    "signatureVerified", "sha1String",
                    "maliciousClassificationType", "createdTime",
                    "modifiedTime", "size", "correctedPath", "productName",
                    "productVersion", "companyName", "internalName",
                    "elementDisplayName"
                ]
            }
            url = "{0}/rest/visualsearch/query/simple".format(
                connector._base_url)

            res = cr_session.post(url, json=query, headers=connector._headers)

            if res.status_code < 200 or res.status_code >= 399:
                connector._process_response(res, action_result)
                return action_result.get_status()

            malops_dict = res.json()["data"]["resultIdToElementDataMap"]

            for file_id, file_data in malops_dict.items():
                connector.save_progress(str(file_id))
                data = {
                    "element_name":
                    file_data["simpleValues"]["elementDisplayName"]["values"]
                    [0],
                    "suspicion_count":
                    file_data.get("suspicionCount")
                }
                self._add_simple_value_if_exists(data, "signed", file_data,
                                                 "isSigned")
                self._add_simple_value_if_exists(data, "SHA1_signature",
                                                 file_data, "sha1String")
                self._add_simple_value_if_exists(data, "size", file_data,
                                                 "size")
                self._add_simple_value_if_exists(data, "path", file_data,
                                                 "correctedPath")
                self._add_simple_value_if_exists(data, "product_name",
                                                 file_data, "productName")
                self._add_simple_value_if_exists(data, "company_name",
                                                 file_data, "companyName")
                action_result.add_data(data)

            summary = action_result.update_summary({})
            summary['total_results'] = len(malops_dict)
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(err)
            connector.debug_print(traceback.format_exc())
            return action_result.set_status(phantom.APP_ERROR,
                                            "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #15
0
    def _handle_query_processes(self, connector, param):
        connector.save_progress("In action handler for: {0}".format(
            connector.get_action_identifier()))

        # Add an action result object to self (BaseConnector) to represent the action for this param
        action_result = connector.add_action_result(ActionResult(dict(param)))

        malop_id = connector._get_string_param(param.get('malop_id'))
        try:
            cr_session = CybereasonSession(connector).get_session()
            query = {
                "queryPath": [{
                    "requestedType": "MalopProcess",
                    "filters": [],
                    "guidList": [malop_id],
                    "connectionFeature": {
                        "elementInstanceType": "MalopProcess",
                        "featureName": "suspects"
                    }
                }, {
                    "requestedType": "Process",
                    "filters": [],
                    "isResult": True
                }],
                "totalResultLimit":
                1000,
                "perGroupLimit":
                100,
                "perFeatureLimit":
                100,
                "templateContext":
                "SPECIFIC",
                "queryTimeout":
                120000,
                "customFields": ["ownerMachine", "elementDisplayName"]
            }
            url = "{0}/rest/visualsearch/query/simple".format(
                connector._base_url)

            res = cr_session.post(url, json=query, headers=connector._headers)

            if res.status_code < 200 or res.status_code >= 399:
                connector._process_response(res, action_result)
                return action_result.get_status()

            processes_dict = res.json()["data"]["resultIdToElementDataMap"]
            for process_id, process_data in processes_dict.items():
                data = {
                    "process_id":
                    process_id,
                    "process_name":
                    process_data["simpleValues"]["elementDisplayName"]
                    ["values"][0]
                }
                self._add_element_value_if_exists(data, "owner_machine_id",
                                                  process_data, "ownerMachine",
                                                  "guid")
                self._add_element_value_if_exists(data, "owner_machine_name",
                                                  process_data, "ownerMachine",
                                                  "name")
                action_result.add_data(data)

            summary = action_result.update_summary({})
            summary['total_processes'] = len(processes_dict)

        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(err)
            connector.debug_print(traceback.format_exc())
            return action_result.set_status(phantom.APP_ERROR,
                                            "Error occurred. {}".format(err))

        return action_result.set_status(phantom.APP_SUCCESS)
コード例 #16
0
    def _get_malop_sensor_ids(self, malop_id, action_result):
        sensor_ids = []
        try:
            cr_session = CybereasonSession(self).get_session()

            url = "{0}/rest/visualsearch/query/simple".format(self._base_url)
            self.save_progress(url)
            query_path = {
                "queryPath": [
                    {
                        "requestedType": "MalopProcess",
                        "filters": [],
                        "guidList": [
                            malop_id
                        ],
                        "connectionFeature": {
                            "elementInstanceType": "MalopProcess",
                            "featureName": "suspects"
                        }
                    },
                    {
                        "requestedType": "Process",
                        "filters": [],
                        "connectionFeature": {
                            "elementInstanceType": "Process",
                            "featureName": "ownerMachine"
                        }
                    },
                    {
                        "requestedType": "Machine",
                        "filters": [],
                        "isResult": True
                    }
                ],
                "totalResultLimit": 1000,
                "perGroupLimit": 1200,
                "perFeatureLimit": 1200,
                "templateContext": "SPECIFIC",
                "queryTimeout": None,
                "customFields": [
                    "pylumId",
                    "elementDisplayName"
                ]
            }
            self.save_progress(str(query_path))
            res = cr_session.post(url, json=query_path, headers=self._headers)

            if res.status_code < 200 or res.status_code >= 399:
                return self._process_response(res, action_result)

            self.save_progress("Got result from /rest/visualsearch/query/simple")
            machines_dict = res.json()["data"]["resultIdToElementDataMap"]
            for _, machine_details in machines_dict.items():
                sensor_ids.append(str(machine_details['simpleValues']['pylumId']['values'][0]))

        except Exception as e:
            err = self._get_error_message_from_exception(e)
            self.save_progress(err)
            return RetVal(action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err)), None)

        return RetVal(action_result.set_status(phantom.APP_SUCCESS), sensor_ids)
コード例 #17
0
class CybereasonPoller:
    def __init__(self):
        return None

    def do_poll(self, connector, param):
        action_result = connector.add_action_result(ActionResult(dict(param)))
        success = True
        try:
            # Declare data that will be lazy-loaded if required
            self.feature_translation = None
            config = connector.get_config()
            state = connector.get_state()
            current_time = datetime.datetime.now()
            is_first_poll = state.get("is_first_poll", True)

            ret_val, malop_historical_days = connector._validate_integer(
                action_result, config["malop_historical_days"],
                MALOP_HISTORICAL_DAYS_KEY)
            if phantom.is_fail(ret_val):
                return action_result.get_status()

            ret_val, malware_historical_days = connector._validate_integer(
                action_result, config["malware_historical_days"],
                MALWARE_HISTORICAL_DAYS_KEY)
            if phantom.is_fail(ret_val):
                return action_result.get_status()

            if is_first_poll:
                connector.save_progress(
                    "This is a first time poll. We will poll for malops from the last {days} days",
                    days=malop_historical_days)
                malop_start_time = current_time + datetime.timedelta(
                    days=-malop_historical_days)
                connector.save_progress(
                    "This is a first time poll. We will poll for malware from the last {days} days",
                    days=malware_historical_days)
                malware_millisec_since_last_poll = malware_historical_days * 60 * 60 * 24 * 1000
                state["is_first_poll"] = False
            else:
                last_poll_timestamp = datetime.datetime.fromtimestamp(
                    state["last_poll_timestamp"])
                malop_start_time = last_poll_timestamp
                malware_millisec_since_last_poll = round(
                    (current_time - last_poll_timestamp).total_seconds() *
                    1000)
            state["last_poll_timestamp"] = current_time.timestamp()
            connector.save_progress(
                "Getting malops between {start_time} and {current_time}",
                start_time=malop_start_time,
                current_time=current_time)
            connector.save_progress(
                "Getting malware for the last {msec} milliseconds",
                msec=malware_millisec_since_last_poll)

            # Initialize the session that will be used throughout the poller
            self.cr_session = CybereasonSession(connector).get_session()
            malop_start_time_microsec_timestamp = round(
                malop_start_time.timestamp() * 1000)
            # When called as a scheduled poll, max_container count comes as 4294967295 which causes a Cybereason API error.
            container_count = min(
                int(param.get(phantom.APP_JSON_CONTAINER_COUNT)), 5000)
            success = success & self._fetch_and_ingest_malops(
                connector, config, malop_start_time_microsec_timestamp,
                container_count)
            success = success & self._fetch_and_ingest_malwares(
                connector, config, malware_millisec_since_last_poll,
                container_count)
        except Exception as e:
            success = False
            err = connector._get_error_message_from_exception(e)
            connector.debug_print("Exception when polling")
            connector.debug_print(err)
            connector.debug_print(traceback.format_exc())
        finally:
            connector.save_state(state)

        if success:
            connector.save_progress(
                "Successfully completed polling for Malop and Malware events")
            return action_result.set_status(
                phantom.APP_SUCCESS,
                "Malop and Malware ingestion completed successfully")
        else:
            return action_result.set_status(
                phantom.APP_ERROR,
                "Error when polling for Malop and Malware. Please refer the logs for more details"
            )

    def _fetch_and_ingest_malops(self, connector, config,
                                 start_time_microsec_timestamp,
                                 container_count):
        # Fetch Malops
        success = True
        malops_dict = self._get_malops(connector,
                                       start_time_microsec_timestamp,
                                       container_count)
        malop_ids = list(malops_dict.keys())
        connector.save_progress(
            "Fetched {number_of_malops} malops from Cybereason console",
            number_of_malops=len(malop_ids))

        # Ingest Malops
        connector.save_progress("Ingesting malops...")
        ingested_count = 0
        percent_complete = 0
        show_progress_after = max(int(len(malop_ids) / 10), 1)
        for malop_id, malop_data in malops_dict.items():
            success = success & self._ingest_malop(connector, config, malop_id,
                                                   malop_data)
            ingested_count = ingested_count + 1
            if ingested_count % show_progress_after == 0:
                percent_complete = round(
                    float(ingested_count) / len(malop_ids) * 100)
                connector.save_progress("{percent_complete}% complete",
                                        percent_complete=percent_complete)
        if percent_complete != 100:
            connector.save_progress("100% complete")
        return success

    def _fetch_and_ingest_malwares(self, connector, config,
                                   malware_millisec_since_last_poll,
                                   container_count):
        # Fetch Malwares
        success = True
        malwares_array = self._get_malware(connector,
                                           malware_millisec_since_last_poll,
                                           container_count)
        connector.save_progress(
            "Fetched {number_of_malwares} malwares from Cybereason console",
            number_of_malwares=len(malwares_array))

        # Ingest malware
        connector.save_progress("Ingesting malware...")
        ingested_count = 0
        percent_complete = 0
        show_progress_after = max(int(len(malwares_array) / 10), 1)
        for malware in malwares_array:
            success = success & self._ingest_malware(connector, config,
                                                     malware)
            ingested_count = ingested_count + 1
            if ingested_count % show_progress_after == 0:
                percent_complete = round(
                    float(ingested_count) / len(malwares_array) * 100)
                connector.save_progress("{percent_complete}% complete",
                                        percent_complete=percent_complete)
        if percent_complete != 100:
            connector.save_progress("100% complete")
        return success

    def _get_decision_feature_translation(self, connector, decision_feature):
        connector.debug_print("Getting decision feature translation table")
        feature_description = decision_feature  # Default to the name of the decision feature
        try:
            if not self.feature_translation:
                url = "{0}/rest/translate/features/all".format(
                    connector._base_url)
                self.feature_translation = self.cr_session.get(url).json()
            # At this point we are guaranteed to have a feature translation
            (decision_feature_type, decision_feature_key
             ) = self._get_decision_feature_details(decision_feature)
            feature_description = self.feature_translation[
                decision_feature_type][decision_feature_key]["translatedName"]
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Warning: Exception when getting feature translation table. {0}"
                .format(err))

        return feature_description

    def _get_decision_feature_details(self, decision_feature):
        # Sample decision_feature is "Process.lsassMemoryAccessMalop(Malop decision)".
        decision_feature_type = decision_feature.split(".")[
            0]  # "Process" in our example
        decision_feature_key = decision_feature.split(".")[1].split("(")[
            0]  # "lsassMemoryAccessMalop" in our example
        return (decision_feature_type, decision_feature_key)

    def _get_sensor_details(self, connector, machine_name):
        url = "{0}/rest/sensors/query".format(connector._base_url)
        query = {
            "filters": [{
                "fieldName": "machineName",
                "operator": "ContainsIgnoreCase",
                "values": [machine_name]
            }, {
                "fieldName": "status",
                "operator": "NotEquals",
                "values": ["Archived"]
            }],
            "sortingFieldName":
            "machineName",
            "sortDirection":
            "ASC",
            "limit":
            500,
            "offset":
            0,
            "batchId":
            None
        }
        sensors = []
        hasMoreSensors = True
        iterCount = 0
        try:
            while hasMoreSensors and iterCount < 100:
                response = self.cr_session.post(url=url,
                                                json=query,
                                                headers=connector._headers)
                result = response.json()
                sensors = sensors + result["sensors"]
                hasMoreSensors = result["hasMoreResults"]
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Unable to fetch sensor details: {0}".format(err))

        return sensors

    def _get_process_details(self, connector, malop_id):
        url = "{0}/rest/visualsearch/query/simple".format(connector._base_url)
        query = {
            "queryPath": [{
                "requestedType": "MalopProcess",
                "filters": [],
                "guidList": [malop_id],
                "connectionFeature": {
                    "elementInstanceType": "MalopProcess",
                    "featureName": "suspects"
                }
            }, {
                "requestedType": "Process",
                "filters": [],
                "isResult": True
            }],
            "totalResultLimit":
            1000,
            "perGroupLimit":
            1200,
            "perFeatureLimit":
            1200,
            "templateContext":
            "SPECIFIC",
            "queryTimeout":
            None,
            "customFields": [
                "imageFile.sha1String", "imageFile.md5String",
                "imageFile.isSigned", "imageFile.productName",
                "calculatedUser", "commandLine", "ownerMachine",
                "creationTime", "elementDisplayName"
            ]
        }
        process_details = {}
        try:
            res = self.cr_session.post(url=url,
                                       json=query,
                                       headers=connector._headers)
            process_details = res.json()["data"]["resultIdToElementDataMap"]
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Error occurred while fetching process details. {}".format(
                    err))

        return process_details

    def _get_connection_details_for_malop(self, connector, malop_id):
        connector.debug_print(
            "Getting connection details for malop {0}".format(malop_id))
        url = "{0}/rest/visualsearch/query/simple".format(connector._base_url)
        query = {
            "queryPath": [{
                "requestedType": "MalopProcess",
                "filters": [],
                "guidList": [malop_id],
                "connectionFeature": {
                    "elementInstanceType": "MalopProcess",
                    "featureName": "suspects"
                }
            }, {
                "requestedType": "Process",
                "filters": [],
                "connectionFeature": {
                    "elementInstanceType": "Process",
                    "featureName": "connections"
                }
            }, {
                "requestedType": "Connection",
                "filters": [],
                "isResult": True
            }],
            "totalResultLimit":
            1000,
            "perGroupLimit":
            1200,
            "perFeatureLimit":
            1200,
            "templateContext":
            "MALOP",
            "queryTimeout":
            None,
            "customFields": [
                "ownerMachine", "ownerProcess.user", "localPort", "remotePort",
                "transportProtocol", "state", "calculatedCreationTime",
                "endTime", "elementDisplayName"
            ]
        }
        connection_details = {}
        try:
            res = self.cr_session.post(url=url,
                                       json=query,
                                       headers=connector._headers)
            connection_details = res.json()["data"]["resultIdToElementDataMap"]
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Error occurred while fetching connection details. {}".format(
                    err))

        return connection_details

    def _get_user_details_for_malop(self, connector, malop_id):
        connector.debug_print(
            "Getting user details for malop {0}".format(malop_id))
        url = "{0}/rest/visualsearch/query/simple".format(connector._base_url)
        query = {
            "queryPath": [{
                "requestedType": "MalopProcess",
                "filters": [],
                "guidList": [malop_id],
                "connectionFeature": {
                    "elementInstanceType": "MalopProcess",
                    "featureName": "suspects"
                }
            }, {
                "requestedType": "Process",
                "filters": [],
                "connectionFeature": {
                    "elementInstanceType": "Process",
                    "featureName": "calculatedUser"
                }
            }, {
                "requestedType": "User",
                "filters": [],
                "isResult": True
            }],
            "totalResultLimit":
            1000,
            "perGroupLimit":
            1200,
            "perFeatureLimit":
            1200,
            "templateContext":
            "MALOP",
            "queryTimeout":
            None,
            "customFields":
            ["isAdmin", "passwordAgeDays", "elementDisplayName"]
        }
        user_details = {}
        try:
            res = self.cr_session.post(url=url,
                                       json=query,
                                       headers=connector._headers)
            user_details = res.json()["data"]["resultIdToElementDataMap"]
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Error occurred while fetching user details. {}".format(err))

        return user_details

    def _ingest_malop(self, connector, config, malop_id, malop_data):
        success = phantom.APP_ERROR
        container = self._get_container_dict_for_malop(connector, config,
                                                       malop_id, malop_data)
        existing_container_id = self._does_container_exist_for_malop_malware(
            connector, malop_id)
        if not existing_container_id:
            # Container does not exist. Go ahead and save it
            connector.debug_print(
                "Saving container for Malop with id {0}".format(malop_id))
            success = connector.save_container(container)
        else:
            # Container exists, which means this Malop has been ingested before. Update it.
            success = self._update_container_for_malop_malware(
                connector, config, existing_container_id, container)

        return phantom.APP_SUCCESS if success else phantom.APP_ERROR

    def _does_container_exist_for_malop_malware(self, connector, malop_id):
        url = '{0}rest/container?_filter_source_data_identifier="{1}"&_filter_asset={2}'.format(
            connector.get_phantom_base_url(), malop_id,
            connector.get_asset_id())

        try:
            r = requests.get(url, verify=False)
            resp_json = r.json()
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Unable to query Cybereason Malop container: {0}".format(err))
            return False

        if resp_json.get("count", 0) <= 0:
            connector.debug_print("No container matched, creating a new one.")
            return False

        try:
            existing_container_id = resp_json.get('data', [])[0]['id']
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Container results are not proper: {0}".format(err))
            return False

        return existing_container_id

    def _update_container_for_malop_malware(self, connector, config,
                                            existing_container_id, container):
        # First, update the container without updating any artifacts
        try:
            connector.debug_print("Updating container for Malop id {0}".format(
                container["source_data_identifier"]))
            update_json = container.copy()
            del update_json["artifacts"]
            url = '{0}rest/container/{1}'.format(
                connector.get_phantom_base_url(), existing_container_id)
            r = requests.post(url, json=update_json, verify=False)
            resp_json = r.json()

            for artifact in container["artifacts"]:
                self._save_or_update_artifact(connector, config,
                                              existing_container_id, artifact)
            if r.status_code != 200 or resp_json.get('failed'):
                connector.debug_print(
                    "Error while updating the container. Error is: ",
                    resp_json.get('failed'))
                return False
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Error occurred while updating the container. {}".format(err))
            return False

        return True

    def _save_or_update_artifact(self, connector, config, container_id,
                                 artifact):
        existing_artifact = self._get_artifact(
            connector, config, artifact["source_data_identifier"],
            container_id)
        if existing_artifact:
            # We have an existing artifact. Update it.
            artifact["container_id"] = existing_artifact["container"]
            artifact["id"] = existing_artifact["id"]
            connector.debug_print(
                'Updating artifact {0}'.format(artifact["name"]), artifact)
            connector.save_artifacts([artifact])
        else:
            # This is a new artifact. Save it directly.
            connector.debug_print(
                'Saving new artifact {0}'.format(artifact["name"]), artifact)
            artifact["container_id"] = container_id
            connector.save_artifact(artifact)

    def _get_artifact(self, connector, config, source_data_identifier,
                      container_id):
        url = '{0}rest/artifact?_filter_source_data_identifier="{1}"&_filter_container_id={2}&sort=id&order=desc'.format(
            connector.get_phantom_base_url(), source_data_identifier,
            container_id)
        try:
            r = requests.get(url, verify=False)
            resp_json = r.json()
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Exception when querying for artifact ID: {0}".format(err))
            return None

        if resp_json.get('count', 0) <= 0:
            connector.debug_print(
                "No artifact matched the source_data_identifier {0} and container id {1}"
                .format(source_data_identifier, container_id))
            return None

        try:
            return resp_json.get('data', [])[0]
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Exception when parsing artifact results: {0}".format(err))
            return None

    def _get_malops(self, connector, malop_timestamp, max_number_malops):
        malops_dict = {}
        url = "{0}/rest/crimes/unified".format(connector._base_url)
        query = {
            "templateContext":
            "OVERVIEW",
            "queryPath": [{
                "requestedType":
                "MalopProcess",
                "guidList": [],
                "filters": [{
                    "values": [malop_timestamp],
                    "filterType": "GreaterThan",
                    "facetName": "malopLastUpdateTime"
                }],
                "result":
                True
            }],
            "totalResultLimit":
            max_number_malops,
            "perGroupLimit":
            max_number_malops,
            "perFeatureLimit":
            max_number_malops
        }
        res = self.cr_session.post(url=url,
                                   json=query,
                                   headers=connector._headers)
        malops_dict = res.json()["data"]["resultIdToElementDataMap"]
        return malops_dict

    def _get_container_dict_for_malop(self, connector, config, malop_id,
                                      malop_data):
        connector.debug_print(
            "Building container for malop {0}".format(malop_id))
        # Build the container JSON
        container_json = {}
        container_json["name"] = malop_data["elementValues"][
            "primaryRootCauseElements"]["elementValues"][0]["name"]
        container_json["data"] = malop_data
        decision_feature = malop_data["simpleValues"]["decisionFeature"][
            "values"][0]
        container_json["description"] = self._get_decision_feature_translation(
            connector, decision_feature)
        container_json["source_data_identifier"] = malop_id
        container_json["label"] = config.get("ingest",
                                             {}).get("container_label")
        status_map = self._get_status_map_malop()
        container_json["status"] = status_map.get(
            malop_data["simpleValues"]["managementStatus"]["values"][0], "New")
        severity_map = self._get_severity_map_malop(connector, config)
        (_, decision_feature_key
         ) = self._get_decision_feature_details(decision_feature)
        container_json["start_time"] = self._phtimestamp_from_crtimestamp(
            malop_data["simpleValues"]["malopStartTime"]["values"][0])
        container_json["severity"] = severity_map.get(decision_feature_key,
                                                      "High")
        container_json["artifacts"] = self._get_artifacts_for_malop(
            connector, malop_id, malop_data)

        return container_json

    def _get_artifacts_for_malop(self, connector, malop_id, malop_data):
        connector.debug_print(
            "Building artifacts for malop {0}".format(malop_id))
        artifacts = []
        artifacts = artifacts + self._get_affected_machines_artifacts(
            connector, malop_data)
        artifacts = artifacts + self._get_affected_users_artifacts(
            connector, malop_id)
        artifacts = artifacts + self._get_suspicious_processes_artifacts(
            connector, malop_id, malop_data)
        artifacts = artifacts + self._get_connection_artifacts(
            connector, malop_id)
        artifacts = artifacts + self._get_comments_artifacts(
            connector, malop_id)
        artifacts = artifacts + self._get_link_to_cr_artifacts(
            connector, malop_id)
        artifacts = artifacts + self._get_last_updated_time_artifact(
            connector, malop_id, malop_data)
        self._add_cef_types_to_artifacts(artifacts)
        return artifacts

    def _get_affected_machines_artifacts(self, connector, malop_data):
        connector.debug_print("Building affected machines artifacts")
        artifacts = []
        for machine in malop_data["elementValues"]["affectedMachines"][
                "elementValues"]:
            affected_machine_artifact = {
                "source_data_identifier": machine["guid"],
                "name": machine["name"],
                "description": "Details of the machine affected by the Malop",
                "type": "machine",
                "label": "machine",
                "cef": {}
            }
            sensors = self._get_sensor_details(connector, machine["name"])
            matching_sensors = [
                s for s in sensors if s["guid"] == machine["guid"]
            ]
            if len(matching_sensors) == 1:
                matching_sensor = matching_sensors[0]
                cef = {}
                cef["osVersion"] = matching_sensor["osVersionType"].replace(
                    "_", " ")
                cef["isolated"] = "Isolated" if matching_sensor[
                    "isolated"] else "Unisolated"
                cef["connectionStatus"] = matching_sensor["status"]
                cef["internalIpAddress"] = matching_sensor["internalIpAddress"]
                affected_machine_artifact["cef"] = cef
            else:
                connector.debug_print(
                    "Unable to get sensor details for machine {0}".format(
                        machine["name"]))
            artifacts.append(affected_machine_artifact)
        return artifacts

    def _get_affected_users_artifacts(self, connector, malop_id):
        connector.debug_print("Building affected users artifacts")
        artifacts = []
        all_user_details = self._get_user_details_for_malop(
            connector, malop_id)
        for _, user_details in all_user_details.items():
            cef = {}
            is_admin_map = {"true": "Admin", "false": "Non-Admin"}
            self._add_simple_value_if_exists(cef, "userType", user_details,
                                             "isAdmin", is_admin_map)
            self._add_simple_value_if_exists(cef, "privileges", user_details,
                                             "privileges")
            self._add_simple_value_if_exists(cef, "passwordAgeDays",
                                             user_details, "passwordAgeDays")

            affected_user_artifact = {
                "source_data_identifier":
                user_details["guidString"],
                "name":
                user_details["simpleValues"]["elementDisplayName"]["values"]
                [0],
                "description":
                "Details of the user affected by the Malop",
                "type":
                "user",
                "label":
                "user",
                "cef":
                cef
            }
            artifacts.append(affected_user_artifact)
        return artifacts

    def _get_suspicious_processes_artifacts(self, connector, malop_id,
                                            malop_data):
        connector.debug_print("Building suspicious processes artifacts")
        artifacts = []
        if not malop_data["elementValues"].get("primaryRootCauseElements"):
            return artifacts

        all_process_details = self._get_process_details(connector, malop_id)
        for _, process_details in all_process_details.items():
            cef = {}
            is_signed_map = {"true": "Signed", "false": "Unsigned"}
            self._add_simple_value_if_exists(cef, "fileHashSha1",
                                             process_details,
                                             "imageFile.sha1String")
            self._add_simple_value_if_exists(cef, "fileHashMD5",
                                             process_details,
                                             "imageFile.md5String")
            self._add_simple_value_if_exists(cef, "signingStatus",
                                             process_details,
                                             "imageFile.isSigned",
                                             is_signed_map)
            self._add_simple_value_if_exists(cef, "productName",
                                             process_details,
                                             "imageFile.productName")
            self._add_element_value_if_exists(cef, "ownerMachineName",
                                              process_details, "ownerMachine",
                                              "name")
            self._add_element_value_if_exists(cef, "ownerMachineGuid",
                                              process_details, "ownerMachine",
                                              "guid")
            self._add_element_value_if_exists(cef, "calculatedUserName",
                                              process_details,
                                              "calculatedUser", "name")
            self._add_element_value_if_exists(cef, "calculatedUserGuid",
                                              process_details,
                                              "calculatedUser", "guid")
            self._add_simple_value_if_exists(cef, "commandLine",
                                             process_details, "commandLine")
            self._add_simple_value_if_exists(cef, "creationTime",
                                             process_details, "creationTime")

            process_artifact = {
                "source_data_identifier":
                process_details["guidString"],
                "name":
                process_details["simpleValues"]["elementDisplayName"]["values"]
                [0],
                "description":
                "Details of the process",
                "type":
                "process",
                "label":
                "process",
                "cef":
                cef
            }

            artifacts.append(process_artifact)
        return artifacts

    def _get_connection_artifacts(self, connector, malop_id):
        connector.debug_print("Building connection artifacts")
        artifacts = []

        for _, connection_details in self._get_connection_details_for_malop(
                connector, malop_id).items():
            cef = {}
            connection_type_map = {"true": "External", "false": "Internal"}
            connection_direction_map = {
                "true": "Incoming",
                "false": "Outgoing"
            }
            connection_process_map = {"true": "Live", "false": "Dead"}

            self._add_simple_value_if_exists(cef, "transportProtocol",
                                             connection_details,
                                             "transportProtocol")
            self._add_simple_value_if_exists(cef, "portType",
                                             connection_details, "portType")
            self._add_simple_value_if_exists(cef, "portDescription",
                                             connection_details,
                                             "portDescription")
            self._add_simple_value_if_exists(cef, "localPort",
                                             connection_details, "localPort")
            self._add_simple_value_if_exists(cef, "remotePort",
                                             connection_details, "remotePort")
            self._add_simple_value_if_exists(cef, "state", connection_details,
                                             "state")
            self._add_simple_value_if_exists(cef, "receivedBytesCount",
                                             connection_details,
                                             "receivedBytesCount")
            self._add_simple_value_if_exists(cef, "transmittedBytesCount",
                                             connection_details,
                                             "transmittedBytesCount")
            self._add_simple_value_if_exists(cef, "remoteAddressCountryName",
                                             connection_details,
                                             "remoteAddressCountryName")
            self._add_simple_value_if_exists(cef, "connectionType",
                                             connection_details,
                                             "isExternalConnection",
                                             connection_type_map)
            self._add_simple_value_if_exists(cef, "direction",
                                             connection_details, "isIncoming",
                                             connection_direction_map)
            self._add_simple_value_if_exists(cef, "connectionStatus",
                                             connection_details,
                                             "isLiveProcess",
                                             connection_process_map)

            # Add remote connection details
            for remote_connection in connection_details["elementValues"][
                    "remoteAddress"]["elementValues"]:
                if remote_connection["elementType"] == "IpAddress":
                    cef["destinationAddress"] = remote_connection["name"]

            connection_artifact = {
                "source_data_identifier":
                connection_details["guidString"],
                "name":
                connection_details["simpleValues"]["elementDisplayName"]
                ["values"][0],
                "description":
                "Details of the connections",
                "type":
                "connection",
                "label":
                "connection",
                "cef":
                cef
            }

            artifacts.append(connection_artifact)
        return artifacts

    def _add_simple_value_if_exists(self,
                                    cef,
                                    cef_key,
                                    obj,
                                    simple_value_key,
                                    transform=None):
        if obj["simpleValues"].get(simple_value_key):
            raw_value = obj["simpleValues"][simple_value_key]["values"][0]
            if transform is None:
                cef[cef_key] = raw_value
            else:
                cef[cef_key] = transform.get(raw_value, 'Undefined')

    def _add_element_value_if_exists(self, cef, cef_key, obj,
                                     element_value_key1, element_value_key2):
        if obj["elementValues"].get(element_value_key1):
            cef[cef_key] = obj["elementValues"][element_value_key1][
                "elementValues"][0][element_value_key2]

    def _get_comments_artifacts(self, connector, malop_id):
        connector.debug_print("Building comments artifacts")
        artifacts = []
        url = "{0}/rest/crimes/get-comments".format(connector._base_url)
        query = malop_id
        try:
            res = self.cr_session.post(url=url,
                                       data=query,
                                       headers=connector._headers)
            comments = res.json()
            for comment in comments:
                cef = {
                    "message": comment["message"],
                    "timestamp": comment["timestamp"]
                }
                comment_artifact = {
                    "source_data_identifier": comment["commentId"],
                    "name": comment["message"],
                    "description": "User comments",
                    "type": "comment",
                    "label": "comment",
                    "cef": cef
                }
                artifacts.append(comment_artifact)
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.debug_print(
                "Error occurred while fetching comment details. {}".format(
                    err))

        return artifacts

    def _get_link_to_cr_artifacts(self, connector, malop_id):
        connector.debug_print("Building link-to-Cybereason-console artifacts")
        artifacts = []
        url = "{0}/#/malop/{1}".format(connector._base_url.rstrip("/"),
                                       malop_id)
        link_artifact = {
            "source_data_identifier": hashlib.sha1(url.encode()).hexdigest(
            ),  # Just using the URL does not work for some reason
            "name": url,
            "description": "Link to view the Malop in the Cybereason console",
            "type": "malop_link",
            "label": "malop_link",
            "cef": {
                "hyperlink": url
            }
        }
        artifacts.append(link_artifact)
        return artifacts

    def _get_last_updated_time_artifact(self, connector, malop_id, malop_data):
        connector.debug_print("Building last updated time artifacts")
        artifacts = []
        link_artifact = {
            "source_data_identifier": malop_id,
            "name": "Last Updated",
            "description": "The time at which this malop was last updated",
            "type": "timestamp",
            "label": "timestamp",
            "cef": {
                "timestamp":
                malop_data["simpleValues"]["malopLastUpdateTime"]["values"][0]
            }
        }
        artifacts.append(link_artifact)
        return artifacts

    def _add_cef_types_to_artifacts(self, artifacts):
        cef_type_map = self._get_cef_type_map()
        for artifact in artifacts:
            cef_keys = list(artifact["cef"].keys())
            artifact["cef_types"] = {}
            for cef_key in cef_keys:
                artifact["cef_types"][cef_key] = cef_type_map.get(cef_key, [])

    def _get_malware(self, connector, malware_millisec_since_last_poll,
                     max_number_malware):
        malwares_array = []
        has_more_malware = True
        offset = 0
        max_malwares_in_each_fetch = min(1000, max_number_malware)
        while has_more_malware and len(malwares_array) < max_number_malware:
            res = self._get_malware_with_offset(
                connector, malware_millisec_since_last_poll,
                max_malwares_in_each_fetch, offset)
            malware_result = res.json()
            malwares_array = malwares_array + malware_result["data"]["malwares"]
            offset = offset + 1
            has_more_malware = malware_result["data"]["hasMoreResults"]
        return malwares_array

    def _get_malware_with_offset(self, connector,
                                 malware_millisec_since_last_poll,
                                 max_malwares_in_each_fetch, offset):
        url = "{0}/rest/malware/query".format(connector._base_url)
        query = {
            "filters": [{
                "fieldName": "timestamp",
                "operator": "FromTimeOp",
                "values": [malware_millisec_since_last_poll]
            }],
            "sortingFieldName":
            "timestamp",
            "sortDirection":
            "ASC",
            "limit":
            max_malwares_in_each_fetch,
            "offset":
            offset
        }
        return self.cr_session.post(url=url,
                                    json=query,
                                    headers=connector._headers,
                                    verify=connector._verify_server_cert)

    def _ingest_malware(self, connector, config, malware):
        success = phantom.APP_ERROR
        container = self._get_container_dict_for_malware(
            connector, config, malware)
        existing_container_id = self._does_container_exist_for_malop_malware(
            connector, malware["guid"])
        if not existing_container_id:
            # Container does not exist. Go ahead and save it
            connector.debug_print(
                "Saving container for Malware with id {}".format(
                    malware["guid"]))
            success = connector.save_container(container)
        else:
            # Container exists, which means this Malop has been ingested before. Update it.
            success = self._update_container_for_malop_malware(
                connector, config, existing_container_id, container)
        return phantom.APP_SUCCESS if success else phantom.APP_ERROR

    def _get_container_dict_for_malware(self, connector, config, malware):
        connector.debug_print("Building container for malware {0}".format(
            malware["guid"]))

        # Build the container JSON
        container_json = {}
        container_json["name"] = "{0}: {1}".format(
            self._get_malware_type_map().get(malware["type"], malware["type"]),
            malware["name"])
        container_json["data"] = malware
        container_json["description"] = malware["name"]
        container_json["source_data_identifier"] = malware["guid"]
        container_json["label"] = config.get("ingest",
                                             {}).get("container_label")
        status_map = self._get_status_map_malware()
        container_json["status"] = status_map.get(malware["status"], "New")
        container_json["start_time"] = self._phtimestamp_from_crtimestamp(
            malware["timestamp"])
        container_json["severity"] = config.get("malware_severity", "High")
        container_json["artifacts"] = [
            self._get_affected_host_artifact_for_malware(connector, malware)
        ]

        return container_json

    def _get_affected_host_artifact_for_malware(self, connector, malware):
        connector.debug_print("Building affected host artifacts")
        cef = {"name": malware["machineName"]}
        composite_uid = "{0} {1}".format(malware["guid"],
                                         malware["machineName"])
        affected_machine_artifact = {
            "source_data_identifier":
            hashlib.sha1(composite_uid.encode()).hexdigest(),
            "name":
            malware["machineName"],
            "description":
            "Details of the machine affected by the Malop",
            "type":
            "machine",
            "label":
            "machine",
            "cef":
            cef
        }
        return affected_machine_artifact

    def _get_status_map_malop(self):
        return {
            "TODO": "New",
            "UNREAD": "New",
            "OPEN": "Open",
            "CLOSED": "Closed",
            "FP": "Closed",
            "REOPEN": "Closed"
        }

    def _get_status_map_malware(self):
        return {
            "Unremediated": "New",
            "Detected": "New",
            "Remediated": "Closed"
        }

    def _get_malware_type_map(self):
        return {
            "KnownMalware": "Known Malware",
            "UnknownMalware": "Unknown Malware",
            "FilelessMalware": "Fileless Malware",
            "ApplicationControlMalware": "Application Control Malware",
            "RansomwareMalware": "Ransomware Malware"
        }

    def _get_severity_map_malop(self, connector, config):
        severity_map = {
            "ransomwareByHashReputation": "High",
            "maliciousHiddenModule": "High",
            "maliciousWebShellExecution": "High",
            "maliciousByCodeInjection": "High",
            "blackListedFileHash": "High",
            "lsassMemoryAccessMalop": "High",
            "maliciousExecutionOfPowerShell": "High",
            "connectionToBlackListAddressByAddressRootCause": "High",
            "maliciousShadowCopyDeletion": "High",
            "connectionToBlackListDomainByDomainRootCause": "High",
            "filelessMalware": "High",
            "credentialTheftMalop": "High",
            "abusingWindowsAccessibilityFeatures": "High",
            "maliciousUseOfOSProcess": "High",
            "jscriptRATMalop": "High",
            "malwareByHashReputation": "Medium",
            "maliciousByOpeningMaliciousFile": "Medium",
            "connectionToMaliciousDomainByDomainRootCause": "Medium",
            "connectionToMaliciousAddressByAddressRootCause": "Medium",
            "maliciousByMalwareModule": "Medium",
            "maliciousByAccessingAddressUsedByMalwares": "Medium",
            "maliciousByDgaDetection": "Medium",
            "maliciousExecutionOfShellProcess": "Medium",
            "maliciousByDualExtensionByFileRootCause": "Low",
            "unwantedByHashReputation": "Low",
            "maliciousByUnwantedModule": "Low"
        }
        try:
            overriden_severity_map = json.loads(
                config.get("override_malop_severity_map", "{}"))
            # If any severities have been overriden, merge them into the default and return that map.
            severity_map.update(overriden_severity_map)
        except Exception as e:
            err = connector._get_error_message_from_exception(e)
            connector.save_progress(
                "Error when merging updated severity map. Proceeding with default map"
            )
            connector.save_progress(err)

        return severity_map

    def _get_cef_type_map(self):
        return {
            "adDNSHostName": ["hostname", "host name"],
            "hash": ["hash"],
            "calculatedUserName": ["user name"],
            "transportProtocol": ["protocol"],
            "localPort": ["port"],
            "remotePort": ["port"],
            "hyperlink": ["url"],
            "internalIpAddress": ["ip"],
            "fileHashSha1": ["hash"],
            "fileHashMD5": ["hash"]
        }

    # Converts timestamps from Cybereason API (e.g. string "1585270873770") to Phantom/ISO 8601 format (e.g. 2020-03-27T01:01:13.770Z)
    def _phtimestamp_from_crtimestamp(self, cybereason_timestamp):
        timestamp = datetime.datetime.fromtimestamp(
            int(cybereason_timestamp) /
            1000.0)  # Timestamp is in epoch-milliseconds
        return timestamp.isoformat(
        )[:-3] + "Z"  # Remove the microsecond accuracy, add "Z" for UTC timezone