Microsoft 365 Defender Event Collector Deprecated
Deprecated. Use 'Office 365' in the XSIAM Data Sources instead.
Analytics & SIEM · Microsoft Defender for Endpoint
Details
| ID | Microsoft 365 Defender Event Collector |
|---|---|
| Provider | Microsoft |
| Category | Analytics & SIEM |
| From Version | 6.8.0 |
| Docker Image | demisto/auth-utils:1.0.0.116752 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
Microsoft Defender for Endpoint Alerts integration for Cortex XSIAM (Deprecated).
Deprecation Announcement
Following this announcement by Microsoft about migrating from the deprecated SIEM API to the Graph API, this Event Collector is now deprecated.
Replacement Option
In XSIAM Office 365 Data Source, select Microsoft Graph API -> Alerts, and select Use Microsoft Graph API V2.
This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.
Configure Microsoft Defender for Endpoint Alerts on Cortex XSIAM
- Navigate to Settings > Integrations > Servers & Services.
- Search for Microsoft Defender for Endpoint Alerts.
-
Click Add instance to create and configure a new integration instance.
Parameter Description Required Endpoint Type The endpoint for accessing Microsoft Defender for Endpoint. See table below. True Client (Application) ID The client (application) ID to use to connect. True Client Secret True Tenant ID True First fetch timestamp (<number> <time unit>, for example 12 hours, 7 days) False Fetch alerts timeout The time limit in seconds for fetch alerts to run. Leave this empty to cancel the timeout limit. False Number of alerts for each fetch. Due to API limitations, the maximum is 10,000. False Fetch events False Verify SSL Certificate False Use system proxy settings False Server URL The United States: api-us.security.microsoft.com
Europe: api-eu.security.microsoft.com
The United Kingdom: api-uk.security.microsoft.com
See table below.True -
Endpoint Type options
Endpoint Type Description Worldwide The publicly accessible Microsoft Defender for Endpoint EU Geo Proximity Microsoft Defender for Endpoint Geo proximity end point for the UK customers. UK Geo Proximity Microsoft Defender for Endpoint Geo proximity end point for the UK customers. US Geo Proximity Microsoft Defender for Endpoint Geo proximity end point for the US customers. US GCC Microsoft Defender for Endpoint for the USA Government Cloud Community (GCC) US GCC-High Microsoft Defender for Endpoint for the USA Government Cloud Community High (GCC-High) DoD Microsoft Defender for Endpoint for the USA Department of Defense (DoD) Custom Custom endpoint configuration to the Microsoft Defender for Endpoint. See note below. - Note: In most cases setting Endpoint type is preferred to setting Server URL. Only use it in cases where a custom URL is required for accessing a national cloud or for cases of self-deployment.
- Click Test to validate the URLs, token, and connection.
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
microsoft-365-defender-get-events
Returns a list of alerts
Base Command
microsoft-365-defender-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of alerts per fetch. Default is 10000. | Optional |
| first_fetch | The first fetch time (<number> <time unit>, for example 12 hours, 1 day, 3 months). Default is 3 days. | Optional |
Context Output
There is no context output for this command.
Context Example
{
"Microsoft365Defender": {
"alerts": [
{
"classification": null,
"investigationState": "TerminatedBySystem",
"computerDnsName": "computer-name",
"evidence": [],
"aadTenantId": "00000000-0000-0000-0000-000000000000",
"id": "aa000000000000000000_000000000",
"category": "SuspiciousActivity",
"threatFamilyName": null,
"lastUpdateTime": "2022-05-12T07:29:45.1466667Z",
"lastEventTime": "2022-05-12T01:19:11.7046854Z",
"firstEventTime": "2022-05-12T01:19:11.7046854Z",
"threatName": null,
"comments": [],
"assignedTo": null,
"detectorId": "00000000-0000-0000-0000-000000000000",
"detectionSource": "AutomatedInvestigation",
"resolvedTime": null,
"alertCreationTime": "2022-05-12T01:19:11.8059246Z",
"status": "New",
"description": "MS description",
"loggedOnUsers": [],
"determination": null,
"severity": "Informational",
"mitreTechniques": [],
"machineId": "abc1234567890987654321234567890987654xyz",
"title": "Automated investigation started manually",
"investigationId": 0000,
"relatedUser": null,
"rbacGroupName": "UnassignedGroup",
"incidentId": 0000
}
]
}
}
microsoft-365-defender-auth-reset
Run this command if for some reason you need to rerun the authentication process.
Base Command
microsoft-365-defender-auth-reset
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
Configuration parameters
endpoint_type— Endpoint Typetenant_id— Tenant ID (required)client_id— Client (Application) ID (required)credentials— (required)first_fetch— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)fetch_timeout— Fetch alerts timeoutlimit— Number of alerts for each fetch.isFetchEvents— Fetch eventsurl— Server URL (e.g., https://api.securitycenter.microsoft.com)verify— Verify SSL Certificateproxy— Use system proxy settings
Commands (2)
-
microsoft-365-defender-auth-resetDeprecatedRun this command if for some reason you need to rerun the authentication process.
-
microsoft-365-defender-get-eventsDeprecatedReturns a list of alerts.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 # pylint: disable=no-name-in-module # pylint: disable=no-self-argument import copy from CommonServerUserPython import * # noqa from abc import ABC from typing import Any from collections.abc import Callable from enum import Enum from pydantic import BaseConfig, BaseModel, AnyUrl, validator # type: ignore[E0611, E0611, E0611] from requests.auth import HTTPBasicAuth import requests import urllib3.util from MicrosoftApiModule import * # Disable insecure warnings urllib3.disable_warnings() # pylint: disable=no-member ''' CONSTANTS ''' MAX_ALERTS_PAGE_SIZE = 1000 ALERT_CREATION_TIME = 'alertCreationTime' DEFENDER_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' AUTH_ERROR_MSG = 'Authorization Error: make sure tenant id, client id and client secret is correctly set' VENDOR = 'Microsoft 365' PRODUCT = 'Defender' ''' HELPER CLASSES ''' # COPY OF SiemApiModule class Method(str, Enum): GET = 'GET' POST = 'POST' PUT = 'PUT' HEAD = 'HEAD' PATCH = 'PATCH' DELETE = 'DELETE' def load_json(v: Any) -> dict: if not isinstance(v, dict | str): raise ValueError('headers are not dict or a valid json') if isinstance(v, str): try: v = json.loads(v) if not isinstance(v, dict): raise ValueError('headers are not from dict type') except json.decoder.JSONDecodeError as exc: raise ValueError('headers are not valid Json object') from exc if isinstance(v, dict): return v return None class IntegrationHTTPRequest(BaseModel): method: Method url: AnyUrl verify: bool = True headers: dict = {} # type: ignore[type-arg] auth: HTTPBasicAuth | None = None data: Any = None class Config(BaseConfig): arbitrary_types_allowed = True _normalize_headers = validator('headers', pre=True, allow_reuse=True)( load_json ) # type: ignore[type-var] class Credentials(BaseModel): identifier: str | None password: str def set_authorization(request: IntegrationHTTPRequest, auth_credentials): """Automatic authorization. Supports {Authorization: Bearer __token__} or Basic Auth. """ creds = Credentials.parse_obj(auth_credentials) if creds.password and creds.identifier: request.auth = HTTPBasicAuth(creds.identifier, creds.password) auth = {'Authorization': f'Bearer {creds.password}'} if request.headers: request.headers |= auth # type: ignore[assignment, operator] else: request.headers = auth # type: ignore[assignment] class IntegrationOptions(BaseModel): """Add here any option you need to add to the logic""" proxy: bool = False limit: int = 1000 class IntegrationEventsClient(ABC): def __init__( self, request: IntegrationHTTPRequest, options: IntegrationOptions, session=requests.Session(), ): self.request = request self.options = options self.session = session self._set_proxy() self._skip_cert_verification() @abstractmethod def set_request_filter(self, after: Any): """TODO: set the next request's filter. Example: """ self.request.headers['after'] = after def __del__(self): try: self.session.close() except AttributeError as err: demisto.debug( f'ignore exceptions raised due to session not used by the client. {err=}' ) def call(self, request: IntegrationHTTPRequest) -> requests.Response: try: response = self.session.request(**request.dict()) response.raise_for_status() return response except Exception as exc: msg = f'something went wrong with the http call {exc}' LOG(msg) raise DemistoException(msg) from exc def _skip_cert_verification( self, skip_cert_verification: Callable = skip_cert_verification ): if not self.request.verify: skip_cert_verification() def _set_proxy(self): if self.options.proxy: ensure_proxy_has_http_prefix() else: skip_proxy() class IntegrationGetEvents(ABC): def __init__( self, client: IntegrationEventsClient, options: IntegrationOptions ) -> None: self.client = client self.options = options def run(self): stored = [] for logs in self._iter_events(): stored.extend(logs) if len(stored) >= self.options.limit: return stored[:self.options.limit] return stored def call(self) -> requests.Response: return self.client.call(self.client.request) @staticmethod @abstractmethod def get_last_run(events: list) -> dict: """Logic to get the last run from the events Example: """ return {'after': events[-1]['created']} @abstractmethod def _iter_events(self): """Create iterators with Yield""" raise NotImplementedError # END COPY OF SiemApiModule class DefenderIntegrationOptions(IntegrationOptions): first_fetch: str class DefenderAuthenticator(BaseModel): verify: bool url: str endpoint_type: str scope_url: str tenant_id: str client_id: str credentials: dict ms_client: Any = None def set_authorization(self, request: IntegrationHTTPRequest): try: if not self.ms_client: demisto.debug(f"try init the ms client for the first time, {self.url=}") self.ms_client = MicrosoftClient( endpoint=self.endpoint_type, base_url=self.url, tenant_id=self.tenant_id, auth_id=self.client_id, enc_key=self.credentials.get('password'), scope=urljoin(self.scope_url, "/windowsatpservice/.default"), verify=self.verify, self_deployed=True, command_prefix="microsoft-365-defender", ) token = self.ms_client.get_access_token() auth = {'Authorization': f'Bearer {token}'} if request.headers: request.headers |= auth # type: ignore[assignment, operator] else: request.headers = auth # type: ignore[assignment] demisto.debug('getting access token for Defender Authenticator - succeeded') except BaseException as e: # catch BaseException to catch also sys.exit via return_error demisto.error(f'Fail to authenticate with Microsoft services: {str(e)}') err_msg = 'Fail to authenticate with Microsoft services, see the error details in the log' raise DemistoException(err_msg) class DefenderHTTPRequest(IntegrationHTTPRequest): params: dict | None = {} method: Method = Method.GET _normalize_url = validator('url', pre=True, allow_reuse=True)( lambda base_url: f'{base_url}/api/alerts' ) # type: ignore[type-var] class DefenderClient(IntegrationEventsClient): authenticator: DefenderAuthenticator request: DefenderHTTPRequest options: DefenderIntegrationOptions def __init__(self, request: DefenderHTTPRequest, options: IntegrationOptions, authenticator: DefenderAuthenticator): self.authenticator = authenticator super().__init__(request, options) def set_request_filter(self, after: Any): limit = min(self.options.limit, MAX_ALERTS_PAGE_SIZE) if not after: demisto.debug(f'lastRunObj is empty, calculate the first fetch time according {self.options.first_fetch=}') first_fetch_date = dateparser.parse(self.options.first_fetch, settings={'TIMEZONE': 'UTC'}) after = datetime.strftime(first_fetch_date, DEFENDER_DATE_FORMAT) # type: ignore[arg-type] self.request.params = { '$filter': f'{ALERT_CREATION_TIME}+gt+{after}', '$orderby': f'{ALERT_CREATION_TIME}+asc', '$top': limit, '$expand': 'evidence', } demisto.debug(f'setting the request filter to be: {self.request.params}') def authenticate(self): self.authenticator.set_authorization(self.request) class DefenderGetEvents(IntegrationGetEvents): client: DefenderClient def _split_evidence(self, org_alerts): """ Extract evidence and create new alert entry that will contain the alert & evidence, for each evidence. """ res: List[Dict] = [] if not org_alerts: return res for alert in org_alerts: evidences = alert.pop('evidence', []) if evidences: for evidence in evidences: updated_alert = alert.copy() updated_alert['evidence'] = evidence res.append(updated_alert) else: alert['evidence'] = {} res.append(alert) return res def _iter_events(self): self.client.authenticate() self.client.set_request_filter(demisto.getLastRun() and demisto.getLastRun().get('after')) response = self.client.call(self.client.request) value = response.json().get('value', []) value = self._split_evidence(value) demisto.debug(f'getting {len(value)} alerts from Defender Api') return [value] @staticmethod def get_last_run(events: list) -> dict: """Logic to get the last run from the events """ return events and len(events) > 0 and {'after': events[-1]['alertCreationTime']} or demisto.getLastRun() ''' HELPER FUNCTIONS ''' ''' COMMAND FUNCTIONS ''' def test_module(get_events: DefenderGetEvents) -> str: """Tests API connectivity and authentication' Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. Raises exceptions if something goes wrong. :type get_events: ``DefenderGetEvents`` :param get_events: the get_events instance :return: 'ok' if test passed, anything else will fail the test. :rtype: ``str`` """ try: get_events.client.request.params = {'limit': 1} get_events.run() return 'ok' except DemistoException as e: if 'Forbidden' in str(e) or 'authenticate' in str(e): return AUTH_ERROR_MSG raise def main(command: str, params: dict): demisto.debug(f'Command being called is {command}') try: params_endpoint_type = params.get('endpoint_type') or 'Worldwide' params_url = params.get('url') # is_gcc wasn't supported in the event collector, thus passing it as None. endpoint_type, params_url = microsoft_defender_for_endpoint_get_base_url(params_endpoint_type, params_url) parsed_params = copy.copy(params) parsed_params["url"] = params_url parsed_params["endpoint_type"] = endpoint_type parsed_params["scope_url"] = MICROSOFT_DEFENDER_FOR_ENDPOINT_APT_SERVICE_ENDPOINTS[endpoint_type] options = DefenderIntegrationOptions.parse_obj(parsed_params) request = DefenderHTTPRequest.parse_obj(parsed_params) authenticator = DefenderAuthenticator.parse_obj(parsed_params) client = DefenderClient(request=request, options=options, authenticator=authenticator) get_events = DefenderGetEvents(client=client, options=options) if command == 'test-module': return_results(test_module(get_events=get_events)) elif command == 'microsoft-365-defender-get-events': events = get_events.run() demisto.debug(f'{command=}, publishing events to the context') human_readable = tableToMarkdown(name="Alerts:", t=events) return_results( CommandResults('Microsoft365Defender.alerts', 'id', events, readable_output=human_readable)) if argToBoolean(params.get('push_to_xsiam', False)): demisto.debug(f'{command=}, publishing events to XSIAM') send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) elif command == 'fetch-events': events = get_events.run() demisto.debug(f'{command=}, publishing events to XSIAM') send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) demisto.setLastRun(get_events.get_last_run(events)) demisto.debug(f'Last run set to {demisto.getLastRun()}') elif command == 'microsoft-365-defender-auth-reset': return_results(reset_auth()) # Log exceptions and return errors except Exception as e: return_error(f'Failed to execute {demisto.command()} command.\nError:\n{str(e)}') ''' ENTRY POINT ''' if __name__ in ('__main__', '__builtin__', 'builtins'): # pragma: no cover # Args is always stronger. Get getIntegrationContext even stronger demisto_params = demisto.params() | demisto.args() | demisto.getLastRun() main(demisto.command(), demisto_params)