Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Credential File Optional #125

Merged
merged 17 commits into from
Jul 25, 2023
2 changes: 1 addition & 1 deletion common.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,4 @@ def sigma_translation(product: str, sigma_rules: list) -> dict:
'title': r.title,
'description': r.description
})
return results
return results
2 changes: 1 addition & 1 deletion help.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ def datetime_to_epoch_millis(date: datetime) -> int:
"""
Convert a datetime object to an epoch timestamp in milliseconds.
"""
return int((date - datetime.utcfromtimestamp(0)).total_seconds() * 1000)
return int((date - datetime.utcfromtimestamp(0)).total_seconds() * 1000)
100 changes: 58 additions & 42 deletions products/cortex_xdr.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -47,63 +47,75 @@ class CortexXDR(Product):
"""
product: str = 'cortex'
creds_file: str # path to credential configuration file
profile:str = "default"
_api_key: str # Required API key
_api_key_id: str # Required API key ID
_url: str # URL of CortexXDR console
_auth_type: Optional[str] = 'standard' # Either standard or advanced, default is standard
_tenant_ids: list[str] = [] # tenant ID list
_session: requests.Session
_queries: dict[Tag, list[Query]]
_last_request: float
_queries: dict[Tag, list[Query]] = dict()
_last_request: float = 0.0
_limit: int = 1000 # Max is 1000 results otherwise have to get the results via stream
_raw: bool = False

def __init__(self, profile: str, creds_file: str, **kwargs):
if not os.path.isfile(creds_file):
raise ValueError(f'Credential file {creds_file} does not exist')
def __init__(self, **kwargs):

self.profile = kwargs['profile'] if 'profile' in kwargs else "default"
self.creds_file = kwargs['creds_file'] if 'creds_file' in kwargs else ''
self._tenant_ids = kwargs['tenant_ids'] if 'tenant_ids' in kwargs else []
self._api_key = kwargs['api_key'] if 'api_key' in kwargs else ''
self._api_key_id = kwargs['api_key_id'] if 'api_key_id' in kwargs else ''
self._url = kwargs['url'] if 'url' in kwargs else ''
self._auth_type = kwargs['auth_type'] if 'auth_type' in kwargs else "standard"
self._raw = kwargs['raw'] if 'raw' in kwargs else self._raw

self.creds_file = creds_file
self._queries = dict()
if self._limit >= int(kwargs.get('limit',0)) > 0:
self._limit = int(kwargs['limit'])
self._last_request = 0.0

super().__init__(self.product, profile, **kwargs)
super().__init__(self.product, **kwargs)

def _authenticate(self) -> None:
config = configparser.ConfigParser()
config.read(self.creds_file)

if self.profile not in config:
raise ValueError(f'Profile {self.profile} is not present in credential file')

section = config[self.profile]

# ensure configuration has required fields
if 'url' not in section:
raise ValueError(f'Cortex XDR configuration invalid, ensure "url" is specified')

# extract required information from configuration
if 'api_key' in section:
self._api_key = section['api_key']
else:
raise ValueError(f'Cortex XDR configuration invalid, ensure "api_key" is specified')
if not (self._url and self._api_key and self._api_key_id and self._auth_type):

if not os.path.isfile(self.creds_file):
raise ValueError(f'Credential file {self.creds_file} does not exist')

elif os.path.isfile(self.creds_file):
config = configparser.ConfigParser()
config.read(self.creds_file)

if self.profile not in config or not self.profile:
raise ValueError(f'Profile {self.profile} is not present in credential file or a profile argument was not passed. Please retry')

section = config[self.profile]

# ensure configuration has required fields
if 'url' not in section:
raise ValueError(f'Cortex XDR configuration invalid, ensure "url" is specified')

# extract required information from configuration
if 'api_key' in section:
self._api_key = section['api_key']
else:
raise ValueError(f'Cortex XDR configuration invalid, ensure "api_key" is specified')

if 'api_key_id' in section:
self._api_key_id = section['api_key_id']
else:
raise ValueError(f'Cortex XDR configuration invalid, ensure "api_key_id" is specified')
if 'api_key_id' in section:
self._api_key_id = section['api_key_id']
else:
raise ValueError(f'Cortex XDR configuration invalid, ensure "api_key_id" is specified')

if 'auth_type' in section:
if section['auth_type'].lower() in ['standard', 'advanced']:
self._auth_type = section['auth_type'].lower()
else:
raise ValueError(
f'Cortex XDR configuration invalid, ensure "auth_type" is one of ["standard","advanced"]')
if 'auth_type' in section:
if section['auth_type'].lower() in ['standard', 'advanced']:
self._auth_type = section['auth_type'].lower()
else:
raise ValueError(
f'Cortex XDR configuration invalid, ensure "auth_type" is one of ["standard","advanced"]')

if 'tenant_id' in section:
self._tenant_ids = section['tenant_id'].split(',')
if 'tenant_id' in section:
self._tenant_ids = section['tenant_id'].split(',')

self._url = section['url'].rstrip('/')
self._url = section['url'].rstrip('/')

if not self._url.startswith('https://'):
raise ValueError(f'URL must start with "https://"')
Expand Down Expand Up @@ -310,8 +322,12 @@ def _process_queries(self) -> None:
event['actor_process_command_line']
additional_data = (event['_time'], event['event_id'])

result = Result(hostname, username, path, commandline, additional_data)
self._results[tag].append(result)
if self._raw:
self._results[tag].append(event)
else:
result = Result(hostname, username, path, commandline, additional_data)
self._results[tag].append(result)

self._queries.clear()

def get_results(self, final_call: bool = True) -> dict[Tag, list[Result]]:
Expand All @@ -325,4 +341,4 @@ def get_results(self, final_call: bool = True) -> dict[Tag, list[Result]]:
return self._results

def get_other_row_headers(self) -> list[str]:
return ['Event Time', 'Event ID']
return ['Event Time', 'Event ID']
70 changes: 48 additions & 22 deletions products/microsoft_defender_for_endpoints.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os

import requests
from typing import Union
from typing import Union,Optional
from common import Product, Tag, Result

PARAMETER_MAPPING: dict[str, dict[str, Union[str, list[str]]]] = {
Expand Down Expand Up @@ -41,38 +41,57 @@ class DefenderForEndpoints(Product):
"""
Surveyor implementation for product "Microsoft Defender For Endpoint"
"""
profile: str = 'default'
product: str = 'dfe'
creds_file: str # path to credential configuration file
_token: str # AAD access token
_limit: int = -1
_tenantId: Optional[str] = None
_appId: Optional[str] = None
_appSecret: Optional[str] = None
_raw: bool = False

def __init__(self, profile: str, creds_file: str, **kwargs):
if not os.path.isfile(creds_file):
raise ValueError(f'Credential file {creds_file} does not exist')
def __init__(self, **kwargs):

self.creds_file = creds_file
self.profile = kwargs['profile'] if 'profile' in kwargs else 'default'
self.creds_file = kwargs['creds_file'] if 'creds_file' in kwargs else ''
self._token = kwargs['token'] if 'token' in kwargs else ''
self._tenantId = kwargs['tenantId'] if 'tenantId' in kwargs else None
self._appId = kwargs['appId'] if 'appId' in kwargs else None
self._appSecret = kwargs['appSecret'] if 'appSecret' in kwargs else None
self._raw = kwargs['raw'] if 'raw' in kwargs else self._raw

if 100000 >= int(kwargs.get('limit', -1)) > self._limit:
self._limit = int(kwargs['limit'])

super().__init__(self.product, profile, **kwargs)
super().__init__(self.product, **kwargs)

def _authenticate(self) -> None:
config = configparser.ConfigParser()
config.sections()
config.read(self.creds_file)

if self.profile not in config:
raise ValueError(f'Profile {self.profile} is not present in credential file')

section = config[self.profile]

if 'token' in section:
self._token = section['token']
elif 'tenantId' not in section or 'appId' not in section or 'appSecret' not in section:
raise ValueError(f'Credential file must contain a token or the fields tenantId, appId, and appSecret values')
else:
self._token = self._get_aad_token(section['tenantId'], section['appId'], section['appSecret'])
if not self._token:

if self._tenantId and self._appId and self._appSecret:
self._token = self._get_aad_token(self._tenantId, self._appId, self._appSecret)

elif not os.path.isfile(self.creds_file):
raise ValueError(f'Credential file {self.creds_file} does not exist')

elif os.path.isfile(self.creds_file):

config = configparser.ConfigParser()
config.sections()
config.read(self.creds_file)

if self.profile not in config:
raise ValueError(f'Profile {self.profile} is not present in credential file')

section = config[self.profile]

if 'token' in section:
self._token = section['token']
elif 'tenantId' not in section or 'appId' not in section or 'appSecret' not in section:
raise ValueError(f'Credential file must contain a token or the fields tenantId, appId, and appSecret values')
else:
self._token = self._get_aad_token(section['tenantId'], section['appId'], section['appSecret'])

def _get_aad_token(self, tenant_id: str, app_id: str, app_secret: str) -> str:
"""
Expand All @@ -95,6 +114,7 @@ def _get_aad_token(self, tenant_id: str, app_id: str, app_secret: str) -> str:
return response.json()['access_token']

def _post_advanced_query(self, data: dict, headers: dict) -> list[Result]:
raw_results = list()
results = set()

try:
Expand All @@ -103,6 +123,8 @@ def _post_advanced_query(self, data: dict, headers: dict) -> list[Result]:

if response.status_code == 200:
for res in response.json()["Results"]:
if self._raw:
raw_results.append(res)
hostname = res['DeviceName'] if 'DeviceName' in res else 'Unknown'
if 'AccountName' in res or 'InitiatingProcessAccountName' in res:
username = res['AccountName'] if 'AccountName' in res else res['InitiatingProcessAccountName']
Expand Down Expand Up @@ -131,6 +153,9 @@ def _post_advanced_query(self, data: dict, headers: dict) -> list[Result]:
self._echo(f"There was an exception {e}")
self.log.exception(e)

if self._raw:
return raw_results

return list(results)

def _get_default_header(self) -> dict[str, str]:
Expand All @@ -141,7 +166,7 @@ def _get_default_header(self) -> dict[str, str]:
}

def process_search(self, tag: Tag, base_query: dict, query: str) -> None:
query = query.rstrip()
query = query.rstrip()

query += f" {self.build_query(base_query)}" if base_query != {} else ''

Expand All @@ -152,6 +177,7 @@ def process_search(self, tag: Tag, base_query: dict, query: str) -> None:
full_query = {'Query': query}

results = self._post_advanced_query(data=full_query, headers=self._get_default_header())

self._add_results(list(results), tag)

def nested_process_search(self, tag: Tag, criteria: dict, base_query: dict) -> None:
Expand Down
Loading