SafeBreach v2 Deprecated
Deprecated. No available replacement.
Data Enrichment & Threat Intelligence · SafeBreach - Breach and Attack Simulation platform · Feed
Details
| ID | SafeBreach v2 |
|---|---|
| Provider | SafeBreach |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 5.5.0 |
| Docker Image | demisto/python3:3.10.12.63474 |
| Supported Modules | Agentix XSIAM |
README
Deprecated. No available replacement.
Configure SafeBreach v2 (Deprecated) for Cortex XSOAR Integration
- Open the Navigation bar → … → CLI Console
- Type config accounts to find out the account id
- Use the id as the accountId parameter in Cortex XSOAR configuration
- Type config apikeys to list existing API keys \
OR \
Add a new one by typing: **config apikeys add –name ** - Use the generated API token as apiKey parameter in Cortex XSOAR configuration
- Use your SafeBreach Management URL as the url parameter in Cortex XSOAR configuration
Configure SafeBreach v2 (Deprecated) on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for SafeBreach v2 (Deprecated).
-
Click Add instance to create and configure a new integration instance.
Parameter Description Required SafeBreach Managment URL For example, https://yourorg.safebreach.com True Account ID Obtained with “config accounts” SafeBreach command True API Key Generated with “config apikeys add” SafeBreach command True Insight Category Insight Data Type Non Behavioral Indicator Reputation Non-Behavioral Indicator from this integration instance will be marked with this reputation Behavioral Reputation Behavioral Indicator from this integration instance will be marked with this reputation Indicators Limit The maximum number of indicators to generate. The default is 1000. Fetch indicators Source Reliability Reliability of the source providing the intelligence data True Traffic Light Protocol Color The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed Feed Fetch Interval Bypass exclusion list When selected, the exclusion list is ignored for indicators from this feed. This means that if an indicator from this feed is on the exclusion list, the indicator might still be added to the system. Trust any certificate (not secure) Use system proxy settings Indicator Reputation Indicators from this integration instance will be marked with this reputation Tags Supports CSV values. - 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.
Configuration parameters
url— SafeBreach Managment URL (required)accountId— Account ID (required)apiKey— API Key (required)insightCategory— Insight CategoryinsightDataType— Insight Data TypenonBehavioralReputation— Non Behavioral Indicator ReputationbehavioralReputation— Behavioral ReputationindicatorLimit— Indicators Limitfeed— Fetch indicatorsfeedReliability— Source Reliability (required)tlp_color— Traffic Light Protocol ColorfeedExpirationPolicy—feedFetchInterval— Feed Fetch IntervalfeedBypassExclusionList— Bypass exclusion listfeedExpirationInterval—insecure— Trust any certificate (not secure)proxy— Use system proxy settingsfeedReputation— Indicator ReputationfeedTags— Tags
Commands (7)
-
safebreach-get-indicatorsDeprecatedFetches SafeBreach Insights from which indicators are extracted, creating new indicators or updating existing indicators.
-
safebreach-get-insightsDeprecatedGets SafeBreach Insights for all security control categories.
-
safebreach-get-remediation-dataDeprecatedGets remediation data for a specific SafeBreach Insight.
-
safebreach-get-simulationDeprecatedGet SafeBreach simulation
-
safebreach-get-test-statusDeprecatedGets the status of a SafeBreach test for tracking progress of a run.
-
safebreach-rerun-insightDeprecatedReruns a specific SafeBreach Insight related simulations in your environment.
-
safebreach-rerun-simulationDeprecatedReruns a specific SafeBreach simulation in your environment.
""" IMPORTS """ from ast import literal_eval from CommonServerPython import * # disable insecure warnings import urllib3 urllib3.disable_warnings() """"" MAPPERS """ CATEGORY_MAPPER: Dict[str, List[int]] = { 'Network Access': [1, 2, 3, 4, 19, 20, 21, 22], 'Network Inspection': [7, 10, 11, 12, 18], 'Endpoint': [8, 9, 13, 14, 17], 'Email': [15, 24], 'Web': [5, 6], 'Data Leak': [16] } SAFEBREACH_TO_DEMISTO_MAPPER = { 'SHA256': 'SHA256', 'Port': 'Port', 'Protocol': 'Protocol', 'FQDN/IP': 'Data', 'Command': 'Command', 'URI': 'URI' } INDICATOR_TYPE_MAPPER: Any = { 'FQDN/IP': FeedIndicatorType.Domain, 'SHA256': FeedIndicatorType.File, 'Domain': FeedIndicatorType.Domain, 'Port': 'SafeBreach Port', 'Protocol': 'SafeBreach Protocol', 'Process': 'SafeBreach Process', 'Registry': 'SafeBreach Registry', 'Command': 'SafeBreach Command', 'URI': FeedIndicatorType.URL, 'IP': FeedIndicatorType.IP, } # mapper from SB data type to demisto data type that given when the integration was configured. INDICATOR_TYPE_SB_TO_DEMISTO_MAPPER = { 'SHA256': 'Hash', 'Port': 'Port', 'FQDN/IP': 'Domain', 'Command': 'Command', 'Protocol': 'Protocol', 'URI': 'URI' } INSIGHT_DATA_TYPE_MAPPER = { 1: 'Port', 2: 'Protocol', 3: 'Port', 4: 'Port', 5: 'Domain', 6: 'URI', 7: 'Hash', 9: 'Hash', 10: 'Protocol', 14: 'Command', 15: 'Hash', 17: 'CVE', # Not Supported Yet. 18: 'CVE', # Not Supported Yet. 19: 'Port', 20: 'Protocol', 21: 'Port', 22: 'Port', 24: 'Hash', } SAFEBREACH_TYPES = [ 'Protocol', 'FQDN/IP', 'Port', 'URI', 'SHA256', 'Attack', 'Proxies', 'Impersonated User', 'Commands', 'Drop Path', 'Registry Path', 'Outbound', 'Inbound', 'Server Header', 'Client Header' ] DEMISTO_INDICATOR_REPUTATION = { 'None': 0, 'Good': 1, 'Suspicious': 2, 'Bad': 3 } IP_REGEX = r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$" class Client: def __init__(self, base_url, account_id, api_key, proxies, verify, tags: Optional[list] = None, tlp_color: Optional[str] = None): self.base_url = base_url self.account_id = account_id self.api_key = api_key self.verify = verify self.tags = [] if tags is None else tags self.tlp_color = tlp_color self.proxies = proxies def http_request(self, method, endpoint_url, url_suffix, body=None): full_url = urljoin(self.base_url, f'{endpoint_url}/v1/accounts/' + f'{self.account_id}{url_suffix}') return requests.request( method, full_url, json=body, headers={'Accept': 'application/json', 'x-apitoken': self.api_key}, verify=self.verify, proxies=self.proxies ) def rerun_at_safebreach(self, rerun_data): return self.http_request('POST', endpoint_url='/api/orch', url_suffix='/queue', body=rerun_data) def get_remediation_data(self, insight_id): return self.http_request('GET', endpoint_url='/api/data', url_suffix=f'/insights/{insight_id}/remediation') def get_insights(self): return self.http_request(method='GET', endpoint_url='/api/data', url_suffix='/insights?type=actionBased') def get_nodes(self): return self.http_request('GET', endpoint_url='api/config', url_suffix='/nodes?details=true&deleted=true&assets=true') def get_simulation(self, simulation_id): return self.http_request(method='GET', endpoint_url='api/data', url_suffix=f'/executions/{simulation_id}') def get_test_status(self, test_id): return self.http_request('GET', endpoint_url='/api/data', url_suffix=f'/testsummaries/{test_id}') def test_connection(self): return self.http_request('GET', endpoint_url='/api/siem', url_suffix='/config/') ''' Helper functions ''' def is_ip(value): if isinstance(value, int): value = str(value) return re.match(IP_REGEX, value) def fix_url(url): if url and url.endswith('/'): return url[0:-1] return url def contains(list_a, list_b): return list(set(list_a) & set(list_b)) def unescape_string(string): try: return string.encode('utf-8').decode('unicode_escape') except Exception as e: demisto.debug(f"Failed to unescape_string: ' {e}") return string def generate_readable_output(data): output = [] types = list(set(map(lambda i: i['type'], data))) for type in types: same_type = list({x['value'] for x in data if x['type'] == type}) output.append({f'{type} ({len(same_type)})': ', '.join(map(str, same_type))}) return output def extract_data(data) -> List[Dict[str, Any]]: output: list = [] list_of_seen_items = [] parent_key = list(data.keys())[0] first_level_data = list(data[parent_key].keys()) list_of_seen_items.extend(first_level_data) if parent_key != 'Attack': output.extend(list(map(lambda o: {'type': parent_key, "value": o}, first_level_data))) for indicator in data[parent_key]: if contains(SAFEBREACH_TYPES, list(data[parent_key][indicator].keys())): for inner_type in data[parent_key][indicator]: formated_inner_type = inner_type.replace(' ', '') for item in data[parent_key][indicator][inner_type]: if item == 'N/A': continue if isinstance(item, str): item = unescape_string(item) if item not in list_of_seen_items: list_of_seen_items.append(item) output.append({'type': formated_inner_type, "value": item}) return output def get_dbot_type(data_type, value): if data_type.lower() in ['sha1', 'md5', 'sha256']: return 'file' if data_type in ['Domain', 'FQDN/IP', 'URI']: if is_ip(value): return 'ip' return 'url' return data_type def get_demisto_context_path(data_type): mapper = { 'SHA256s': 'File(val.SHA256 == obj.SHA256)', 'SHA256': 'File(val.SHA256 == obj.SHA256)', 'FQDNs/IPs': 'URL(val.Data == obj.Data)', 'FQDN/IP': 'URL(val.Data == obj.Data)', 'IP': 'URL(val.Data == obj.Data)', 'Domain': 'URL(val.Data == obj.Data)', 'Commands': 'Process(val.CommandLine == obj.CommandLine)', 'URIs': 'URL(val.URI == obj.URI)', 'URI': 'URL(val.URI == obj.URI)' } return mapper.get(data_type) def get_insights_ids_by_category(insight_category: List[str]) -> Union[List[int], None]: output: Any = [] for category in insight_category: if CATEGORY_MAPPER.get(category): output.append(CATEGORY_MAPPER.get(category)) return list(set([y for x in output for y in x])) def refactor_rerun_data(rerun_data, simulation): rerun_name = f'Rerun (Demisto) - #({rerun_data.get("matrix").get("moveIds")[0]}) {simulation.get("moveName")}' return { "force": True, "matrix": { "name": rerun_name, "moveIds": rerun_data['matrix']['moveIds'], "nodeIds": list(set(rerun_data['matrix']['nodeIds'])), "simulationId": simulation.get('id') } } def fetch_simulation_result(simulation): if simulation['status'] == 'SUCCESS': return 'Not-Blocked' if simulation['status'] == 'FAIL': return 'Blocked' return 'Failure' def get_node_details(node_role, simulation): id = simulation.get('attackerNodeId') if node_role == 'Attacker' else simulation.get('targetNodeId') for item in simulation.get('dataObj').get('data')[0]: if id == item['id']: return item['details'] return "" def get_mitre_details(simulation): return [list(map(lambda tech: tech['value'], simulation.get('MITRE_Technique') or [])), list(map(lambda group: group['value'], simulation.get('Threat_Actor') or [])), list(map(lambda soft: soft['value'], simulation.get('MITRE_Software') or []))] def get_node_display_name(node_role, simulation): node_name = simulation.get(f'{node_role}NodeName') internal_ip = simulation.get(f'{node_role}InternalIp') external_ip = simulation.get(f'{node_role}ExternalIp') return f"{node_name} ({internal_ip},{external_ip})" def get_category_and_data_type_filters(args, predefine_insight_category, predefine_insight_data_type): insight_category = args.get('insightCategory') or predefine_insight_category insight_data_type = args.get('insightDataType') or predefine_insight_data_type # The User can provide the arguments as the following: insightCategory=`Web,Network Inspection` if isinstance(insight_category, str): insight_category = insight_category.split(',') if isinstance(insight_data_type, str): insight_data_type = insight_data_type.split(',') # if the user provide invalid category or data type raise an ValueError. if not contains(insight_category, ['Network Access', 'Network Inspection', 'Endpoint', 'Email', 'Web', 'Data Leak']): raise ValueError(f'Category {insight_category} is not a valid category') if not contains(insight_data_type, ['Hash', 'Domain', 'URI', 'Command', 'Port', 'Protocol']): raise ValueError(f'Data type {insight_data_type} is not a valid data type') return insight_category, insight_data_type def get_node_ids_from_insight(insight): node_ids = [] for target in insight.get('targets'): node_ids.append(target.get('targetNodeId')) for attacker in target['attackers']: node_ids.append(attacker.get('attackerNodeId')) return list(set(node_ids)) def extract_affected_targets(client, insight): all_nodes = {item['id']: item for item in client.get_nodes().json()['data']} return list(map(lambda t: {'name': all_nodes[t['targetNodeId']]['name'], 'ip': all_nodes[t['targetNodeId']]['externalIp'], 'count': t['breakdown']['count']}, insight['targets'])) def get_splunk_remedation_query(response): try: query = [vendor['searchQuery'] for vendor in response.json()['mitigationVendors'] if vendor['id'] == 'splunk'] return "".join(query) except Exception as e: demisto.error(e) return "" def extract_safebreach_error(response): errors = response.json().get('error') and response.json().get('error').get('errors') if not errors: return f'Failed to extract error!\n{response.json().get("error")}' return ','.join(list(map(lambda e: e.get('data').get('message'), errors))) ''' Commands ''' def get_indicators_command(client: Client, insight_category: list, insight_data_type: list, tlp_color: Optional[str], args: dict) -> List[Dict]: """Create indicators. Arguments: client {Client} -- Client derives from BaseClient. insight_category {List[String]} -- List of SafeBreach insight category - using as filter. insight_data_type {List[String]} -- List of data types - using as filter. tlp_color {str}: Traffic Light Protocol color. Keyword Arguments: Returns: List[Dict] -- List of insights from SafeBreach """ limit: int = int(args.get('limit') or demisto.params().get('indicatorLimit', 1000)) indicators: List[Dict] = [] count: int = 0 # These variable be filled directly from the integration configuration or as arguments. insight_category, insight_data_type = get_category_and_data_type_filters(args, insight_category, insight_data_type) # Convert category into insight id insights_ids: Any = get_insights_ids_by_category(insight_category) raw_insights: Any = client.get_insights().json() # Filter insight by category insights: Any = [item for item in raw_insights if int(item.get('ruleId')) in insights_ids] for insight in insights: # Fetch remediation data for each insight processed_data: List[Dict[str, Any]] = get_remediation_data_command(client, {'insightId': insight.get('ruleId')}, False) for item in processed_data: # if the data type is not in the filter data types continue, if INDICATOR_TYPE_SB_TO_DEMISTO_MAPPER.get(item['type']) not in insight_data_type: continue if not INDICATOR_TYPE_MAPPER.get(str(item['type'])) or item["value"] == 'N/A': continue if isinstance(item['type'], int): demisto.info('Data type is int', item['type'], insight['ruleId']) is_behaveioral = item['type'] not in ['Domain', 'FQDN/IP', 'SHA256', 'URI', 'Hash'] score_behavioral_reputation = DEMISTO_INDICATOR_REPUTATION.get(demisto.params().get('behavioralReputation')) score_non_behavioral_reputation = DEMISTO_INDICATOR_REPUTATION.get( demisto.params().get('nonBehavioralReputation')) raw_json = { 'value': str(item["value"]), 'dataType': item['type'], 'insightId': insight.get('ruleId'), 'insightTime': insight.get('maxExecutionTime'), } mapping = { 'description': 'SafeBreach Insight - {0}'.format(insight['actionBasedTitle']), item['type'].lower(): item["value"], "safebreachinsightids": str(insight.get('ruleId')), "safebreachseverity": insight.get('severity'), "safebreachseverityscore": str(insight.get('severityScore')), "safebreachisbehavioral": is_behaveioral, "safebreachattackids": list(map(str, insight.get('attacks'))), 'tags': [ f"SafeBreachInsightId: {insight.get('ruleId')}", ] } if tlp_color: mapping['trafficlightprotocol'] = tlp_color mapping['tags'] = list((set(mapping['tags'])).union(set(client.tags))) indicator = { 'value': str(item["value"]), 'type': INDICATOR_TYPE_MAPPER.get(str(item['type'])), 'rawJSON': raw_json, 'fields': mapping, 'score': score_behavioral_reputation if is_behaveioral else score_non_behavioral_reputation } if is_ip(item["value"]): indicator['type'] = FeedIndicatorType.IP count += 1 if count > limit: return indicators indicators.append(indicator) return indicators def get_remediation_data_command(client: Client, args: dict, no_output_mode: bool) -> List[Dict[str, Any]]: """Get SafeBreach remediation data. Arguments: client {Client} -- Client derives from BaseClient args {dict} -- function arguments no_output_mode {bool} -- if true, this function will insert data to the context, otherwise, it will just returns the data. Keyword Arguments: Returns: Dict -- Each key is a unique SafeBreach data type. Each value is a list of the data. """ insight_id: Optional[int] = args.get('insightId') response = client.get_remediation_data(insight_id) insight: Any = get_insights_command(client, {'insightIds': [insight_id]}, False) if insight: insight = insight[0] if response.status_code < 200 or response.status_code >= 300: error = extract_safebreach_error(response) raise DemistoException( f'Failed to fetch remediation data for insight id {insight_id}\nSafeBreach error:{error}') sb_remediation_data = response.json().get('remediationData') processed_data = extract_data(sb_remediation_data) readable_output_list = generate_readable_output(processed_data) vendor_remediation_data = list(filter(lambda o: o['value'], [{'type': "Splunk", "value": get_splunk_remedation_query(response)}])) # Demisto Context: dbot_score_list = [] standard_context_dict = {} standard_context_list: Any = [] secondary_standard_context_dict: Any = {} secondary_standard_context_list = [] secondary_path: Any = None # SafeBreach Context: safebreach_context_list = [] safebreach_context = { "Id": insight_id, 'RawRemediationData': processed_data, 'VendorRemediationData': vendor_remediation_data } safebreach_context_list.append(safebreach_context) for item in processed_data: if item.get('type', '').startswith('Attack') or len(processed_data) == 0: continue demisto_standard_path = get_demisto_context_path(item['type']) # e.g URL(val.Data == obj.Data) demisto_data_type = SAFEBREACH_TO_DEMISTO_MAPPER.get(item['type']) # SHA256,Port,Protocol,Data,Command,URI if item['type'] in ['DropPaths', 'URIs', 'URI', 'Command']: try: item["value"] = item["value"].encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf-8') except Exception as e: demisto.debug(f"Failed to decode/encode: ' {e}") item["value"] = item["value"] if demisto_data_type: is_behaveioral = item['type'] not in ['Domain', 'FQDN/IP', 'SHA256', 'URI', 'Hash'] score_behavioral_reputation = DEMISTO_INDICATOR_REPUTATION.get(demisto.params().get('behavioralReputation')) score_non_behavioral_reputation = DEMISTO_INDICATOR_REPUTATION.get( demisto.params().get('nonBehavioralReputation')) dbot_score = { "Indicator": item["value"], 'type': get_dbot_type(item['type'], item["value"]), # TODO: maybe change it to SB_Indicator? "Vendor": "SafeBreach", "Score": score_behavioral_reputation if is_behaveioral else score_non_behavioral_reputation } primary_standard_context = { demisto_data_type: item["value"], # e.g Data : <URL>, SHA256:<SHA256> "Malicious": { "Description": f"SafeBreach Insights - ({insight_id}){insight.get('actionBasedTitle')}", "Vendor": "SafeBreach" } } if item['type'] in ['FQDNs/IPs', 'FQDN/IP']: if re.match(IP_REGEX, item["value"]): secondary_path = 'IP(val.Address == obj.Address)' secondary_standard_context_dict = { 'IP': item["value"], "Malicious": { "Description": f"SafeBreach Insights - ({insight_id}){insight.get('actionBasedTitle')}", "Vendor": "SafeBreach" } } else: secondary_path = 'Domain(val.Name == obj.Name)' secondary_standard_context_dict = { 'Name': item["value"], "Malicious": { "Description": f"SafeBreach Insights - ({insight_id}){insight.get('actionBasedTitle')}", "Vendor": "SafeBreach" } } if demisto_standard_path: standard_context_list.append(primary_standard_context) secondary_standard_context_list.append(secondary_standard_context_dict) dbot_score_list.append(dbot_score) if len(standard_context_list) > 0 and demisto_standard_path: standard_context_dict[demisto_standard_path] = standard_context_list if secondary_path: standard_context_dict[secondary_path] = secondary_standard_context_list output_context = { "DBotScore(val.Indicator == obj.Indicator)": dbot_score_list, "SafeBreach.Insight(val.Id == obj.Id)": safebreach_context_list } merged_context = {**output_context, **standard_context_dict} readable_output = tableToMarkdown(name="Remediation Data", t=readable_output_list, removeNull=True) if no_output_mode: return_outputs(readable_output=readable_output, outputs=merged_context) return processed_data def insight_rerun_command(client: Client, args: dict): """Rerun SafeBreach insight. Arguments: client {Client} -- Client derives from BaseClient args {dict} -- function arguments Keyword Arguments: insight_id {list<int>} -- The insight id to rerun Returns: outputs """ raw_insights: Any = client.get_insights().json() insight_ids: Any = args.get('insightIds') if not insight_ids: raise Exception('insightIds was not provided to the command') human_readable_list = [] safebreach_insight_context_list = [] safebreach_test_context_list = [] if isinstance(insight_ids, str): insight_ids = literal_eval(insight_ids) if isinstance(insight_ids, int): insight_ids = [insight_ids] insight_ids = [int(id) for id in insight_ids] # Filter all insight according to given. active_insight_ids = list(map(lambda i: i['ruleId'], raw_insights)) invalid_insight_ids = list(filter(lambda i: i not in active_insight_ids, insight_ids)) insights = list(filter(lambda insight: insight['ruleId'] in insight_ids, raw_insights)) # if given id is not in the active id: if len(invalid_insight_ids): # TODO: check if it can be demisto.log() demisto.info(f'Insight ids:{invalid_insight_ids} are invalid.') for insight in insights: try: insight_id = insight.get('ruleId') if not insight_ids: raise ValueError('Insight IDs are invalid') nodes_ids = get_node_ids_from_insight(insight) rerun_data = { "matrix": { "name": "Insight (XSOAR) - {0}".format(insight['actionBasedTitle']), "moveIds": insight['attacks'], "nodeIds": nodes_ids }, "force": True } response = client.rerun_at_safebreach(rerun_data) if response.status_code < 200 or response.status_code >= 300: error = extract_safebreach_error(response) raise DemistoException( f'Failed to rerun simulation for insight id {insight_id}\nSafeBreach error:{error}') try: response = response.json()['data'] except ValueError: raise ValueError('Response body does not contain valid json') t = { 'Insight Id': insight_id, 'Test Id': response.get('runId'), 'Name': "Insight (XSOAR) - {0}".format(insight.get('actionBasedTitle')), '# Attacks': len(insight.get('attacks')) } context_object = { 'Id': insight_id, 'Rerun': [{'Name': "Insight (XSOAR) - {0}".format(insight.get('actionBasedTitle')), 'Id': response.get('runId'), 'AttacksCount': len(insight.get('attacks')), 'ScheduledTime': datetime.now().isoformat()}] } test_context_dict = { 'Id': response.get('runId'), 'Name': "Insight (XSOAR) - {0}".format(insight.get('actionBasedTitle')), 'Status': 'Pending', 'AttacksCount': len(insight.get('attacks')), 'ScheduledTime': datetime.now().isoformat() } human_readable_list.append(t) safebreach_insight_context_list.append(context_object) safebreach_test_context_list.append(test_context_dict) except Exception as e: traceback.print_exc() return_error('Failed to rerun insight', e) safebreach_context = { 'SafeBreach.Insight(val.Id == obj.Id)': safebreach_insight_context_list, 'SafeBreach.Test(val.Id == obj.Id)': safebreach_test_context_list, } readable_output = tableToMarkdown(name='Rerun SafeBreach Insight', t=human_readable_list, removeNull=True) return_outputs(readable_output=readable_output, outputs=safebreach_context) def get_insights_command(client: Client, args: Dict, no_output_mode: bool) -> List: """Get SafeBreach insights. Arguments: client {Client} -- Client derives from BaseClient args {dict} -- function arguments Keyword Arguments: Returns: List[Dict] -- List of insights from SafeBreach """ insight_ids = args.get('insightIds') insights: Any if isinstance(insight_ids, str): insight_ids = literal_eval(insight_ids) if isinstance(insight_ids, int): insight_ids = [insight_ids] response: Any = client.get_insights() if response.status_code < 200 or response.status_code >= 300: raise ValueError('Failed to fetch SafeBreach insights', response) try: insights = sorted(response.json(), key=lambda i: i.get('ruleId')) except TypeError: demisto.info('Failed to sort SafeBreach insights, skip') if insight_ids and len(insight_ids) > 0: # Verify that insight_ids holds List[int] if isinstance(insight_ids, list): insight_ids = list(map(int, insight_ids)) insights = [item for item in insights if int(item.get('ruleId')) in insight_ids] insight_output = [] insight_readable = [] headers = [] for insight in insights: affected_targets = extract_affected_targets(client, insight) context_insight = { 'Name': insight['actionBasedTitle'], 'Id': insight['ruleId'], 'DataType': INSIGHT_DATA_TYPE_MAPPER.get(insight['ruleId']) or 'Other', 'Category': insight.get('category'), 'LatestSimulation': insight.get('maxExecutionTime'), 'EarliestSimulation': insight.get('minExecutionTime'), 'SimulationsCount': insight.get('context').get('simulationsCount'), 'RiskImpact': float("{0:.2f}".format(insight.get('impact'))), 'AffectedTargetsCount': len(insight.get('targets')), 'SeverityScore': insight.get('severityScore'), 'Severity': insight.get('severity'), 'RemediationDataCount': insight.get("mitigationPoints").get('value'), 'RemediationDataType': insight.get("mitigationPoints").get('key'), 'ThreatGroups': insight.get('threatActors'), 'NetworkDirection': insight.get('direction'), 'AttacksCount': len(insight.get('attacks')), 'AttackIds': insight.get('attacks'), 'AffectedTargets': affected_targets, 'RemediationAction': insight.get('action'), 'ResultLink': f"{fix_url(demisto.params().get('url'))}/#/executions?query={insight.get('criteria')}" } t = { 'Id': context_insight['Id'], 'Name': context_insight['Name'], 'Category': context_insight['Category'], 'Risk Impact': context_insight['RiskImpact'], 'Severity': context_insight['Severity'], 'Affected Targets': context_insight['AffectedTargetsCount'], 'Data Type': context_insight['DataType'], } headers = list(t.keys()) insight_output.append(context_insight) insight_readable.append(t) readable_output = tableToMarkdown(name='SafeBreach Insights', t=insight_readable, headers=headers, removeNull=True) outputs = { 'SafeBreach.Insight(val.Id == obj.Id)': insight_output } if no_output_mode: return_outputs( readable_output, outputs, insight_output ) return insights def get_test_status_command(client: Client, args: Dict): """Get status of a SafeBreach test for tracking progress of a run. Arguments: client {Client} -- Client derives from BaseClient args {dict} -- function's arguments Keyword Arguments: testId : float Returns: None """ test_ids = argToList(args.get('testId')) for test_id in test_ids: tries = 0 response: Any while tries < 3: response = client.get_test_status(test_id) if response.status_code == 200: break tries += 1 if response.status_code < 200 or response.status_code >= 300: raise ValueError(f'Failed to get status of test: {test_id}') try: response = response.json() except ValueError: raise ValueError('Response body does not contain valid json') t = { 'Test Id': response.get('id'), 'Name': response.get('matrixName'), 'Status': response.get('status'), 'Start Time': response.get('startTime'), 'End Time': response.get('endTime'), 'Total Simulation Number': response.get('blocked', 0) + response.get('notBlocked', 0) } readable_output = tableToMarkdown(name='Test Status', t=t, headers=list(t.keys()), removeNull=True) safebreach_context = { "SafeBreach.Test(val.Id == obj.Id)": { 'Id': response.get('id'), 'Name': response.get('matrixName'), 'Status': response.get('status'), 'StartTime': response.get('startTime'), 'EndTime': response.get('endTime'), 'TotalSimulationNumber': response.get('blocked', 0) + response.get('notBlocked', 0) } } return_outputs(readable_output=readable_output, outputs=safebreach_context) def get_safebreach_simulation_command(client: Client, args: Dict): """Get SafeBreach simulation. Arguments: client {Client} -- Client derives from BaseClient args {dict} -- function arguments Keyword Arguments: simulationId {str} -- simulation id. Returns: None """ simulation_id = args.get('simulationId') if not simulation_id: raise ValueError('No simulation ID') response = client.get_simulation(simulation_id) if response.status_code < 200 or response.status_code >= 300: raise ValueError('Failed to fetch SafeBreach simulation') try: simulation = response.json() except ValueError: raise ValueError('Response body does not contain valid json') mitre_techniques, mitre_groups, mitre_software = get_mitre_details(simulation) final_status = simulation.get('finalStatus') if final_status is not None: final_status = final_status.capitalize() detected_action = simulation.get('siemDetectionStatus') if detected_action is not None: detected_action = detected_action.capitalize() try: simulation_context = { 'Id': simulation.get('id'), 'FinalStatus': final_status, 'Result': fetch_simulation_result(simulation), 'DetectedAction': detected_action, 'SimulationRunId': simulation.get('jobId'), 'Time': simulation.get('executionTime'), 'LastChangeTime': simulation.get('lastStatusChangeDate'), 'Labels': simulation.get('labels'), "Parameters": simulation.get('parameters'), 'Attack': { 'Id': simulation.get('moveId'), 'Name': simulation.get('moveName'), 'Description': simulation.get('moveDesc'), 'Phase': simulation.get('packageName'), 'Type': list(map(lambda attack_type: attack_type['value'], simulation.get('Attack_Type'))), 'SecurityControl': list(map(lambda item: item['value'], simulation.get('Security_Controls'))), 'IndicatorBased': 'True' if simulation.get('IoC_Based')[0]['value'] == 1 else 'False' }, 'Attacker': { 'Name': simulation.get('attackerNodeName'), 'OS': simulation.get('attackerOSType'), 'InternalIp': simulation.get('attackerInternalIp'), 'ExternalIp': simulation.get('attackerExternalIp'), 'SimulationDetails': get_node_details('Attacker', simulation), }, 'Target': { 'Name': simulation.get('targetNodeName'), 'OS': simulation.get('targetOSType'), 'InternalIp': simulation.get('targetInternalIp'), 'ExternalIp': simulation.get('targetExternalIp'), 'SimulationDetails': get_node_details('Target', simulation), }, 'Network': { 'Direction': simulation.get('direction'), 'SourceIp': simulation.get('sourceIp'), 'DestinationIp': simulation.get('destinationIp'), 'SourcePort': simulation.get('sourcePort'), 'DestinationPort': simulation.get('serverPort'), 'Protocol': simulation.get('attackProtocol'), 'Proxy': simulation.get('parameters').get('PROXY') and simulation['parameters']['PROXY'][0][ 'displayName'], }, 'Classifications': { 'MITRETechniques': mitre_techniques, 'MITREGroups': mitre_groups, 'MITRESoftware': mitre_software } } t = { 'Id': simulation_id, 'Name': f'(#{simulation.get("moveId")}) {simulation.get("moveName")}', 'Status': simulation.get('status').capitalize(), 'Result': simulation.get('status').capitalize(), 'Detected Action': simulation.get('status').capitalize(), 'Attacker': get_node_display_name('attacker', simulation), 'Target': get_node_display_name('target', simulation), } human_readable = tableToMarkdown(name='SafeBreach Simulation', t=t, headers=list(t.keys()), removeNull=True) outputs = { 'SafeBreach.Simulation(val.Id == obj.Id)': simulation_context } return_outputs(readable_output=human_readable, outputs=outputs) except Exception: traceback.print_exc() def rerun_simulation_command(client: Client, args: dict): """Rerun a specific SafeBreach simulation in your environment. Arguments: client {Client} -- Client derives from BaseClient args {dict} -- function arguments Keyword Arguments: simulationId {str} -- The id of the simulation to rerun. Returns: None """ rerun_data = {} simulation_id = args.get('simulationId') if not simulation_id: raise ValueError("No simulation ID has been provided") try: response = client.get_simulation(simulation_id) if response.status_code < 200 or response.status_code >= 300: raise ValueError(f'Cant Find Simualtion id :{simulation_id}') simulation = response.json() rerun_data = simulation.get('rerun') if not rerun_data: raise ValueError(f'Cant Find Rerun Data for simulation is:{simulation_id}') rerun_data = refactor_rerun_data(rerun_data, simulation) response = client.rerun_at_safebreach(rerun_data).json()['data'] t = { 'Simulation Id': simulation_id, 'Test Id': response['runId'], 'Name': rerun_data['matrix']['name'], } safebreach_context = { "Id": simulation_id, "Rerun": { 'Id': response['runId'], 'Name': rerun_data['matrix']['name'], 'ScheduledTime': datetime.now().isoformat() } } test_context_dict = { 'Id': response['runId'], 'Name': rerun_data['matrix']['name'], 'Status': 'PENDING', 'AttacksCount': len(rerun_data['matrix']['moveIds']) } human_readable = tableToMarkdown(name='SafeBreach Rerun Simualtion', t=t, headers=list(t.keys()), removeNull=True) safebreach_context = { 'SafeBreach.Simulation(val.Id == obj.Id)': safebreach_context, 'SafeBreach.Test(val.Id == obj.Id)': test_context_dict, } return_outputs(readable_output=human_readable, outputs=safebreach_context) except Exception as e: traceback.print_exc() return_error('Error in rerun_simulation', e) def safebreach_test_module(client: Client) -> str: """A simple test module Arguments: url {String} -- SafeBreach Management URL. Returns: str -- "ok" if succeeded, else raises a error. """ response = client.test_connection() if response.status_code == 200: return 'ok' else: massage = response.reason.lower() if response.status_code == 401: massage = 'API Key is invalid, try again' elif response.status_code == 404: massage = 'URL is invalid, try again' elif response.status_code < 200 or response.status_code >= 300: massage = 'Test connection failed' raise Exception(f'{massage}') def main(): command = demisto.command() params = demisto.params() account_id = params.get('accountId') api_key = params.get('apiKey') url = fix_url(params.get('url')) insight_category_filter = params.get('insightCategory') insight_data_type_filter = params.get('insightDataType') tags = argToList(params.get('feedTags')) tlp_color = params.get('tlp_color') verify_certificate = not params.get('insecure', False) proxies = handle_proxy() try: client = Client(base_url=url, account_id=account_id, api_key=api_key, proxies=proxies, verify=verify_certificate, tags=tags) if command == 'safebreach-get-insights': get_insights_command(client, demisto.args(), True) elif command == 'safebreach-rerun-insight': insight_rerun_command(client, demisto.args()) elif command == 'safebreach-get-remediation-data': get_remediation_data_command(client, demisto.args(), True) elif command == 'safebreach-get-test-status': get_test_status_command(client, demisto.args()) elif command == 'safebreach-get-simulation': get_safebreach_simulation_command(client, demisto.args()) elif command == 'safebreach-rerun-simulation': rerun_simulation_command(client, demisto.args()) elif command == 'fetch-indicators': indicators = get_indicators_command(client, insight_category_filter, insight_data_type_filter, tlp_color, demisto.args()) for b in batch(indicators, batch_size=2000): demisto.createIndicators(b) # type: ignore demisto.results('ok') elif command == 'safebreach-get-indicators': indicators = get_indicators_command(client, insight_category_filter, insight_data_type_filter, tlp_color, demisto.args()) entry_result = camelize(indicators) hr = tableToMarkdown('Indicators:', entry_result) return_outputs(hr, {}, entry_result) elif command == 'test-module': results = safebreach_test_module(client) return_outputs(results) else: return_error(f'Command: {command} is not supported.') except Exception as e: return_error(str(e)) # python2 uses __builtin__ python3 uses builtins if __name__ in ['__main__', "__builtin__", "builtins"]: main()