Details
| ID | MISP Feed |
|---|---|
| Provider | OpenSource |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 5.5.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Indicators feed from MISP.
This integration was integrated and tested with version 1.0 of MISP Feed.
MISP Feed integration allows you to ingest feeds into TIM via an MISP instance.
To ingest feeds via MISP, you must first configure a MISP instance and have the proper credentials.
To ingest specific feeds (Bambenek Consulting Feed, BruteForceBlocker Feed, etc.) directly to TIM without any authorization, you can use one of our dedicated feed content packs available in Marketplace.
To ingest feeds via a URL, you could use one of the following content packs:
- CSV Feed
- JSON Feed
- Plain Text Feed
- RSS Feed
Configure MISP Feed in Cortex
| Parameter | Description | Required |
|---|---|---|
| Your server URL | True | |
| API Key | The API Key to use for the connection. | True |
| Client Certificate | False | |
| Private Key | False | |
| Timeout | The timeout of the HTTP requests sent to the MISP API (in seconds). If no value is provided, the timeout will be set to 60 seconds. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch indicators | False | |
| Indicator Reputation | Indicators from this integration instance will be marked with this reputation. | False |
| Source Reliability | Reliability of the source providing the intelligence data. | True |
| Feed Fetch Interval | False | |
| 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. | False |
| Max. indicators per fetch | Limit the number of indicators retrieved in a fetch run. | False |
| MISP Attribute Tags | Attributes having one of the tags, or being an attribute of an event having one of the tags, will be returned. You can enter a comma-separated list of tags, for example <tag1,tag2,tag3>. The list of MISP tags can be found in your MISP instance under ‘Event Actions’>’List Tags’ | False |
| MISP Attribute Types | Attributes of one of these types will be returned. You can enter a comma-separated list of types, for example <type1,type2,type3>. The list of MISP types can be found in your MISP instance then ‘Event Actions’>’Search Attributes’>’Type dropdown list’ | False |
| Query | JSON query to filter MISP attributes. When the query parameter is used, Attribute Types and Attribute Tags parameters are not used. You can check for the correct syntax at https://<Your MISP url>/servers/openapi#operation/restSearchAttributes | False |
| Traffic Light Protocol Color | The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. | False |
| Tags | Supports CSV values. | False |
Commands
You can execute these commands from the 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.
misp-feed-get-indicators
Gets indicators from the feed.
Base Command
misp-feed-get-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of results to return. Default is 10. | Optional |
| tags | Tags of the attributes to search for. | Optional |
| attribute_type | Types of the attributes to search for. | Optional |
| query | JSON query to filter MISP attributes. When a query argument is used attribute_type and tags arguments are not used. You can check for the correct syntax at https://<Your MISP url>/servers/openapi#operation/restSearchAttributes. | Optional |
Context Output
{
"MISPFeed": {
"Indicators": {
"0": {
"fields": {
"Category": "Payload delivery",
"Description": "desc",
"SHA256": "somehash",
"Updated Date": 1607517728,
"trafficlightprotocol": "GREEN"
},
"rawJSON": {
"FeedURL": "someurl",
"type": "File",
"value": {
"Event": {
"distribution": 1,
"id": 123,
"info": "some info",
"org_id": 1,
"orgc_id": 7,
"uuid": "some uuid"
},
"category": "Payload delivery",
"comment": "desc",
"deleted": false,
"disable_correlation": false,
"distribution": 5,
"event_id": 143,
"first_seen": null,
"id": 69548,
"last_seen": null,
"object_id": 0,
"object_relation": null,
"sharing_group_id": 0,
"timestamp": 1607517728,
"to_ids": true,
"type": "sha256",
"uuid": "some uuid",
"value": "some hash"
}
},
"service": "MISP",
"type": "File",
"value":"somehash"
}
}
}
}
Command Example
!misp-feed-get-indicators tags=tlp:% attribute_type=ip-src
Human Readable Output
Retrieved 7 indicators.
Additional Information
If you experience a timeout error while fetching indicators, the following query configuration may be helpful for you.
You can configure the feed to return results from the last 24 hours as shown below:
{
"returnFormat": "json",
"type": {
"OR": []
},
"tags": {
"OR": []
},
"last":"24h"
}
Both of the above queries can be modified however you would like depending on your specific use case. Additional information can be found here https://www.misp-project.org/openapi/#tag/Attributes/operation/restSearchAttributes
Configuration parameters
url— Server URL (required)credentials— (required)certificate— Client Certificatemax_indicator_to_fetch— Max. indicators per fetchtimeout— Timeoutfeed— Fetch indicatorsfeedIncremental— Incremental FeedfeedReputation— Indicator Reputationperformance— Remove rawJSON from indicatorsfeedReliability— Source Reliability (required)feedExpirationPolicy—feedExpirationInterval—feedFetchInterval— Feed Fetch IntervalfeedBypassExclusionList— Bypass exclusion listattribute_tags— MISP Attribute Tagsattribute_types— MISP Attribute Typesquery— Querytlp_color— Traffic Light Protocol ColorfeedTags— Tagsproxy— Use system proxy settingsinsecure— Trust any certificate (not secure)
Commands (1)
-
misp-feed-get-indicatorsGets indicators from the feed.
import demistomock as demisto # noqa: F401 import urllib3 import os import tempfile from CommonServerPython import * # noqa: F401 # disable insecure warnings urllib3.disable_warnings() class TempFile: def __init__(self, data): _, self.path = tempfile.mkstemp() with open(self.path, "w") as temp_file: temp_file.write(data) def __del__(self): os.remove(self.path) INDICATOR_TO_GALAXY_RELATION_DICT: Dict[str, Any] = { ThreatIntel.ObjectsNames.ATTACK_PATTERN: { FeedIndicatorType.File: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IP: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.CIDR: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IPv6: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IPv6CIDR: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Domain: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.URL: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Email: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Registry: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.THREAT_ACTOR: EntityRelationship.Relationships.USES, DBotScoreType.CRYPTOCURRENCY: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.MALWARE: EntityRelationship.Relationships.USES, ThreatIntel.ObjectsNames.CAMPAIGN: EntityRelationship.Relationships.USES, }, ThreatIntel.ObjectsNames.MALWARE: { FeedIndicatorType.File: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IP: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.CIDR: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IPv6: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IPv6CIDR: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Domain: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.URL: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Email: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Registry: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.THREAT_ACTOR: EntityRelationship.Relationships.USES, DBotScoreType.CRYPTOCURRENCY: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.MALWARE: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.CAMPAIGN: EntityRelationship.Relationships.USES, }, ThreatIntel.ObjectsNames.TOOL: { FeedIndicatorType.File: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IP: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.CIDR: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IPv6: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IPv6CIDR: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Domain: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.URL: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Email: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Registry: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.THREAT_ACTOR: EntityRelationship.Relationships.USES, DBotScoreType.CRYPTOCURRENCY: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.MALWARE: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.CAMPAIGN: EntityRelationship.Relationships.USES, }, ThreatIntel.ObjectsNames.INTRUSION_SET: { FeedIndicatorType.File: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IP: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.CIDR: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IPv6: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.IPv6CIDR: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Domain: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.URL: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Email: EntityRelationship.Relationships.INDICATOR_OF, FeedIndicatorType.Registry: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.THREAT_ACTOR: EntityRelationship.Relationships.RELATED_TO, DBotScoreType.CRYPTOCURRENCY: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.MALWARE: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.CAMPAIGN: EntityRelationship.Relationships.ATTRIBUTED_TO, }, ThreatIntel.ObjectsNames.COURSE_OF_ACTION: { FeedIndicatorType.File: EntityRelationship.Relationships.RELATED_TO, FeedIndicatorType.IP: EntityRelationship.Relationships.RELATED_TO, FeedIndicatorType.CIDR: EntityRelationship.Relationships.RELATED_TO, FeedIndicatorType.IPv6: EntityRelationship.Relationships.RELATED_TO, FeedIndicatorType.IPv6CIDR: EntityRelationship.Relationships.RELATED_TO, FeedIndicatorType.Domain: EntityRelationship.Relationships.RELATED_TO, FeedIndicatorType.URL: EntityRelationship.Relationships.RELATED_TO, FeedIndicatorType.Email: EntityRelationship.Relationships.RELATED_TO, FeedIndicatorType.Registry: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.THREAT_ACTOR: EntityRelationship.Relationships.RELATED_TO, DBotScoreType.CRYPTOCURRENCY: EntityRelationship.Relationships.RELATED_TO, ThreatIntel.ObjectsNames.MALWARE: EntityRelationship.Relationships.MITIGATED_BY, ThreatIntel.ObjectsNames.CAMPAIGN: EntityRelationship.Relationships.RELATED_TO, }, } ATTRIBUTE_TO_INDICATOR_MAP = { "sha256": FeedIndicatorType.File, "md5": FeedIndicatorType.File, "sha1": FeedIndicatorType.File, "filename|md5": FeedIndicatorType.File, "filename|sha1": FeedIndicatorType.File, "filename|sha256": FeedIndicatorType.File, "domain": FeedIndicatorType.Domain, "email": FeedIndicatorType.Email, "email-src": FeedIndicatorType.Email, "email-dst": FeedIndicatorType.Email, "url": FeedIndicatorType.URL, "regkey": FeedIndicatorType.Registry, "threat-actor": ThreatIntel.ObjectsNames.THREAT_ACTOR, "btc": DBotScoreType.CRYPTOCURRENCY, "campaign-name": ThreatIntel.ObjectsNames.CAMPAIGN, "campaign-id": ThreatIntel.ObjectsNames.CAMPAIGN, "malware-type": ThreatIntel.ObjectsNames.MALWARE, "hostname": FeedIndicatorType.Domain, } GALAXY_MAP = { "misp-galaxy:mitre-attack-pattern": ThreatIntel.ObjectsNames.ATTACK_PATTERN, "misp-galaxy:mitre-malware": ThreatIntel.ObjectsNames.MALWARE, "misp-galaxy:mitre-tool": ThreatIntel.ObjectsNames.TOOL, "misp-galaxy:mitre-intrusion-set": ThreatIntel.ObjectsNames.INTRUSION_SET, "misp-galaxy:mitre-course-of-action": ThreatIntel.ObjectsNames.COURSE_OF_ACTION, } LIMIT: int = 2000 class Client(BaseClient): def __init__( self, base_url: str, authorization: str, timeout: float, verify: bool, proxy: bool, performance: bool, max_indicator_to_fetch: Optional[int], client_cert: Optional[tuple] = None, ): super().__init__(base_url=base_url, verify=verify, proxy=proxy) self.timeout = timeout self.client_cert = client_cert self._headers = { "Authorization": authorization, "Accept": "application/json", "Content-Type": "application/json", } self.performance = performance self.max_indicator_to_fetch = max_indicator_to_fetch def search_query(self, body: Dict[str, Any]) -> Dict[str, Any]: """ Creates a request to MISP to get all attributes filtered by query in the body argument Args: body: Dictionary containing query to filter MISP attributes. Returns: bytes representing the response from MISP API """ return self._http_request( "POST", url_suffix="/attributes/restSearch", resp_type="json", data=json.dumps(body), timeout=self.timeout, cert=self.client_cert, ) """ Helper Functions """ def build_indicators_iterator(attributes: Dict[str, Any], url: Optional[str]) -> List[Dict[str, Any]]: """ Creates a list of valid indicators types to be created Args: attributes: List of attributes returned from MISP url: Feed URL Returns: List of indicators and their types """ indicators_iterator = [] try: attributes_list: List[Dict[str, Any]] = attributes["response"]["Attribute"] for attribute in attributes_list: if indicator_type := get_attribute_indicator_type(attribute): indicators_iterator.append( { "value": attribute, "type": indicator_type, "raw_type": attribute["type"], "FeedURL": url, } ) except KeyError as err: demisto.debug(str(err)) raise KeyError(f"Could not parse returned data as attributes list. \nError massage: {err}") return indicators_iterator def handle_tags_fields(indicator_obj: Dict[str, Any], tags: List[Any], feed_tags: Optional[List]) -> None: """ Adds tags to the indicator if they're a valid tag Args: indicator_obj: Indicator currently being built tags: List of tags of the attribute retrieved from MISP feed_tags: custom tags to be added to the created indicator Returns: None """ indicator_obj["fields"]["Tags"] = [] for tag in tags: tag_name = tag.get("name", None) if tag_name and not get_galaxy_indicator_type(tag_name): indicator_obj["fields"]["Tags"].append(tag_name) indicator_obj["fields"]["Tags"].extend(feed_tags) def handle_file_type_fields(raw_type: str, indicator_obj: Dict[str, Any]) -> None: """ If the attribute is of type sha1,sha256 or MD5 - will add SHA1 or SHA256 or MD5 field and their value to the indicator. If the attribute type is 'filename|<sha1/sha256/md5>' will add the filename to Associated File Names field, will update the indicator value and will add the hash field. Args: raw_type: Type of the attribute indicator_obj: Indicator currently being built Returns: None """ hash_value = indicator_obj["value"] if "filename|" in raw_type: pipe_index = hash_value.index("|") filename = hash_value[0:pipe_index] hash_value = hash_value[pipe_index + 1 :] indicator_obj["fields"]["Associated File Names"] = filename indicator_obj["value"] = hash_value raw_type = raw_type[raw_type.index("|") + 1 :] indicator_obj["fields"][raw_type.upper()] = hash_value def build_params_dict( tags: List[str], attribute_type: List[str], limit: int, page: int, from_timestamp: Optional[int] = None ) -> Dict[str, Any]: """ Creates a dictionary in the format required by MISP to be used as a query. Args: tags: List of tags to filter by attribute_type: List of types to filter by Returns: Dictionary used as a search query for MISP """ params: Dict[str, Any] = { "returnFormat": "json", "type": { "OR": attribute_type if attribute_type else [], }, "tags": { "OR": tags if tags else [], }, "limit": limit, "page": page, "includeEventTags": True, } if from_timestamp: params["attribute_timestamp"] = str(from_timestamp) return params def parsing_user_query(query: str, limit: int, page: int = 1, from_timestamp: Optional[int] | None = None) -> Dict[str, Any]: """ Parsing the query string created by the user by adding necessary argument and removing unnecessary arguments Args: query: User's query string Returns: Dict which has only needed arguments to be sent to MISP """ try: params = json.loads(query) params["returnFormat"] = "json" if "page" not in params: params["page"] = page params["limit"] = params.get("limit") or limit if params.get("timestamp"): params["attribute_timestamp"] = params.pop("timestamp") if from_timestamp: params["attribute_timestamp"] = str(from_timestamp) except Exception as err: demisto.debug(str(err)) raise DemistoException(f"Could not parse user query. \nError massage: {err}") # NOTE: include event-level inherited tags which MISP strips by default; # setdefault preserves any explicit value from the user's `query`. params.setdefault("includeEventTags", True) return params def get_attributes_from_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: """ Safely extracts the list of attributes from a MISP search response. Args: response: The raw response returned from client.search_query. Returns: The list of attribute dicts, or an empty list if none are present. """ return response.get("response", {}).get("Attribute", []) def get_ip_type(ip_attribute: Dict[str, Any]) -> str: """ Returns the correct FeedIndicatorType for attributes of type ip Args: ip_attribute: the ip attribute Returns: FeedIndicatorType """ return FeedIndicatorType.ip_to_indicator_type(ip_attribute["value"]) def get_attribute_indicator_type(attribute: Dict[str, Any]) -> Optional[str]: """ Gets the correct Indicator type that matches the attribute type, attribute type is not supported returns None Args: attribute: Dictionary containing information about the attribute Returns: The matching indicator type or None if the attribute type is not supported """ attribute_type = attribute["type"] if attribute_type == "ip-src" or attribute_type == "ip-dst": return get_ip_type(attribute) else: return ATTRIBUTE_TO_INDICATOR_MAP.get(attribute_type, None) def get_galaxy_indicator_type(galaxy_tag_name: str) -> Optional[str]: """ Returns an Indicator type matching to the galaxy type Args: galaxy_tag_name: name of the galaxy Returns: type of the indicator if there's one matching to the provided galaxy or None """ if "galaxy" in galaxy_tag_name: galaxy_name = galaxy_tag_name[0 : galaxy_tag_name.index("=")] return GALAXY_MAP.get(galaxy_name, None) return None def build_indicator(value_: str, type_: str, raw_data: Dict[str, Any], reputation: Optional[str]) -> Dict[str, Any]: """ Creates an indicator object Args: value_: value of the indicator type_: type of the indicator raw_data: raw data of the indicator reputation: string representing reputation of the indicator Returns: Dictionray which is the indicator object """ indicator_obj = { "value": value_, "type": type_, "service": "MISP", "fields": {}, "rawJSON": raw_data, "Reputation": reputation, } return indicator_obj def build_indicators( client: Client, response: Dict[str, Any], attribute_type: List[str], tlp_color: Optional[str], url: Optional[str], reputation: Optional[str], feed_tags: Optional[List], ) -> List[Dict]: indicators_iterator = build_indicators_iterator(response, url) indicators = [] for indicator in indicators_iterator: value_ = indicator["value"]["value"] type_ = indicator["type"] raw_type = indicator.pop("raw_type") indicator_obj = build_indicator(value_, type_, indicator, reputation) update_indicator_fields(indicator_obj, tlp_color, raw_type, feed_tags) galaxy_indicators = build_indicators_from_galaxies(indicator_obj, reputation) create_and_add_relationships(indicator_obj, galaxy_indicators) if client.performance: indicator_obj.pop("rawJSON") indicators.append(indicator_obj) return indicators def build_indicators_from_galaxies(indicator_obj: Dict[str, Any], reputation: Optional[str]) -> List[Dict[str, Any]]: """ Builds indicators from the galaxy tags in the attribute Args: indicator_obj: Indicator being built reputation: string representing reputation of the indicator Returns: List of indicators created from the galaxies """ tags = indicator_obj["rawJSON"]["value"].get("Tag", []) galaxy_indicators = [] for tag in tags: tag_name = tag.get("name", None) type_ = get_galaxy_indicator_type(tag_name) if tag_name and type_: try: value_ = tag_name[tag_name.index("=") + 2 : tag_name.index(" -")] except ValueError: demisto.debug(f"A ValueError was raised on {tag_name=}, of type {type_}, trying to parse with no -") value_ = tag_name[tag_name.index("=") + 2 : tag_name.rindex('"')] galaxy_indicators.append(build_indicator(value_, type_, tag, reputation)) continue galaxy_indicators.append(build_indicator(value_, type_, tag, reputation)) return galaxy_indicators def create_and_add_relationships(indicator_obj: Dict[str, Any], galaxy_indicators: List[Dict[str, Any]]) -> None: """ Creates relationships between the indicators created from the attributes and the indicators created from the galaxies Args: indicator_obj: Indicator being built galaxy_indicators: List of indicators created from the galaxies Returns: None """ indicator_obj_type = indicator_obj["type"] relationships_indicators = [] for galaxy_indicator in galaxy_indicators: galaxy_indicator_type = galaxy_indicator["type"] indicator_to_galaxy_relation = INDICATOR_TO_GALAXY_RELATION_DICT[galaxy_indicator_type][indicator_obj_type] galaxy_to_indicator_relation = EntityRelationship.Relationships.RELATIONSHIPS_NAMES[indicator_to_galaxy_relation] indicator_relation = EntityRelationship( name=indicator_to_galaxy_relation, entity_a=indicator_obj["value"], entity_a_type=indicator_obj_type, entity_b=galaxy_indicator["value"], entity_b_type=galaxy_indicator_type, ).to_indicator() galaxy_relation = EntityRelationship( name=galaxy_to_indicator_relation, entity_a=galaxy_indicator["value"], entity_a_type=galaxy_indicator_type, entity_b=indicator_obj["value"], entity_b_type=indicator_obj_type, ).to_indicator() relationships_indicators.append(indicator_relation) galaxy_indicator["Relationships"] = [galaxy_relation] if relationships_indicators: indicator_obj["Relationships"] = relationships_indicators def update_indicator_fields( indicator_obj: Dict[str, Any], tlp_color: Optional[str], raw_type: str, feed_tags: Optional[List] ) -> None: """ Updating required fields of the indicator with values from the attribute Args: indicator_obj: Indicator being built tlp_color: Traffic Light Protocol color. raw_type: Type of the attribute feed_tags: Custom tags to be added to the created indicator Returns: None """ raw_json_value = indicator_obj["rawJSON"]["value"] first_seen = raw_json_value.get("first_seen", None) last_seen = raw_json_value.get("last_seen", None) timestamp = raw_json_value.get("timestamp", None) category = raw_json_value.get("category", None) comment = raw_json_value.get("comment", None) tags = raw_json_value.get("Tag", []) or [] if first_seen: indicator_obj["fields"]["First Seen By Source"] = first_seen if last_seen: indicator_obj["fields"]["Last Seen By Source"] = last_seen if timestamp: indicator_obj["fields"]["Updated Date"] = timestamp if category: indicator_obj["fields"]["Category"] = category if comment: indicator_obj["fields"]["Description"] = comment if tlp_color: indicator_obj["fields"]["trafficlightprotocol"] = tlp_color if tags or feed_tags: handle_tags_fields(indicator_obj, tags, feed_tags) if "md5" in raw_type or "sha1" in raw_type or "sha256" in raw_type: handle_file_type_fields(raw_type, indicator_obj) """ Command Functions """ def test_module(client: Client) -> str: """ Fetch a single feed item to assure configuration is valid. Args: client: Client object. Returns: ok if feed is accessible """ client.search_query(body={"returnFormat": "json", "limit": 1}) return "ok" def get_attributes_command(client: Client, args: Dict[str, str], params: Dict[str, str]) -> CommandResults: """Wrapper for fetching indicators from the feed to the war-room. Args: client: Client object with request args: demisto.args() params: demisto.params() Returns: CommandResults object containing the indicators retrieved """ limit = arg_to_number(args.get("limit", "10")) or 10 tlp_color = params.get("tlp_color") reputation = params.get("feedReputation") tags = argToList(args.get("tags", "")) feed_tags = argToList(params.get("feedTags", [])) query = args.get("query", None) attribute_type = argToList(args.get("attribute_type", "")) page = arg_to_number(args.get("page")) or 1 params_dict = ( parsing_user_query(query, limit, page) if query else build_params_dict(tags=tags, attribute_type=attribute_type, limit=limit, page=page) ) response = client.search_query(params_dict) if error_message := response.get("Error"): raise DemistoException(error_message) indicators = build_indicators(client, response, attribute_type, tlp_color, params.get("url"), reputation, feed_tags) hr_indicators = [] for indicator in indicators: hr_indicators.append( { "Value": indicator.get("value"), "Type": indicator.get("type"), "rawJSON": indicator.get("rawJSON"), "fields": indicator.get("fields"), } ) human_readable = tableToMarkdown( "Indicators from MISP:", hr_indicators, headers=["Value", "Type", "rawJSON", "fields"], removeNull=True ) return CommandResults( readable_output=human_readable, outputs_prefix="MISPFeed.Indicators", outputs_key_field="value", raw_response=indicators, ) def update_candidate( last_run: dict, last_run_timestamp: Optional[int], latest_indicator_timestamp: Optional[int], latest_indicator_value: str ): """ Update the candidate timestamp and value based on the latest and last run values. Args: last_run: a dictionary containing the last run information, including the timestamp, page, and indicator value. last_run_timestamp: the timestamp of the last run. latest_indicator_timestamp: the timestamp of the latest indicator. latest_indicator_value: the value of the latest indicator. """ candidate_timestamp = last_run.get("candidate_timestamp") or last_run_timestamp if not candidate_timestamp or (latest_indicator_timestamp and latest_indicator_timestamp > candidate_timestamp): last_run["candidate_timestamp"] = latest_indicator_timestamp last_run["candidate_value"] = latest_indicator_value def fetch_attributes_command(client: Client, params: Dict[str, str]): """ Fetching indicators from the feed to the Indicators tab. Args: client: Client object with request params: demisto.params() Returns: List of indicators. """ tlp_color = params.get("tlp_color") reputation = params.get("feedReputation") tags = argToList(params.get("attribute_tags", "")) feed_tags = argToList(params.get("feedTags", [])) attribute_types = argToList(params.get("attribute_types", "")) fetch_limit = client.max_indicator_to_fetch or LIMIT last_run = demisto.getLastRun() total_fetched_indicators = 0 query = params.get("query", None) last_run_timestamp = arg_to_number(last_run.get("last_indicator_timestamp")) last_run_page = last_run.get("page") or 1 last_run_value = last_run.get("last_indicator_value") or "" params_dict = ( parsing_user_query(query, fetch_limit, from_timestamp=last_run_timestamp, page=last_run_page) if query else build_params_dict( tags=tags, attribute_type=attribute_types, limit=fetch_limit, page=last_run_page, from_timestamp=last_run_timestamp ) ) search_query_per_page = client.search_query(params_dict) demisto.debug(f"params_dict: {params_dict}") attributes = get_attributes_from_response(search_query_per_page) while attributes: demisto.debug(f"search_query_per_page number of attributes: {len(attributes)} page: {params_dict['page']}") attributes.sort(key=lambda x: x["timestamp"], reverse=False) indicators = build_indicators( client, search_query_per_page, attribute_types, tlp_color, params.get("url"), reputation, feed_tags ) total_fetched_indicators += len(indicators) latest_indicator = attributes latest_indicator_timestamp = arg_to_number(latest_indicator[-1]["timestamp"]) latest_indicator_value = latest_indicator[-1]["value"] if last_run_timestamp == latest_indicator_timestamp and latest_indicator_value == last_run_value: # No new indicators since last run, no need to fetch again demisto.debug("No new indicators found since last run") return for iter_ in batch(indicators, batch_size=2000): demisto.createIndicators(iter_) params_dict["page"] += 1 update_candidate(last_run, last_run_timestamp, latest_indicator_timestamp, latest_indicator_value) # Note: The limit is applied after indicators are created, # so the total number of indicators may slightly exceed the limit due to page size constraints. if fetch_limit and fetch_limit <= total_fetched_indicators: demisto.setLastRun(last_run | {"page": params_dict["page"]}) demisto.debug( f"Reached the limit of indicators to fetch. The number of indicators fetched is: {total_fetched_indicators}" ) return search_query_per_page = client.search_query(params_dict) attributes = get_attributes_from_response(search_query_per_page) if error_message := search_query_per_page.get("Error"): raise DemistoException(f"Error in API call - check the input parameters and the API Key. Error: {error_message}") candidate_timestamp = last_run.get("candidate_timestamp") candidate_value = last_run.get("candidate_value") if candidate_timestamp is None: # No candidate was produced this run. demisto.debug("No candidate produced this run; leaving existing lastRun unchanged") return demisto.setLastRun({"last_indicator_timestamp": candidate_timestamp, "last_indicator_value": candidate_value}) def main(): # pragma: no cover params = demisto.params() base_url = params.get("url").rstrip("/") timeout = arg_to_number(params.get("timeout", 60)) or 60 insecure = not params.get("insecure", False) proxy = params.get("proxy", False) performance = argToBoolean(params.get("performance") or False) max_indicator_to_fetch = arg_to_number(x) if (x := params.get("max_indicator_to_fetch")) else None command = demisto.command() args = demisto.args() if params.get("feedExpirationPolicy") == "suddenDeath": raise DemistoException("The feed is incremental, so a sudden-death policy is not applicable.") # Handle client certificate certificate = replace_spaces_in_credential(params.get("certificate", {}).get("identifier")) private_key = replace_spaces_in_credential(params.get("certificate", {}).get("password")) cert = TempFile(certificate) if certificate else None key = TempFile(private_key) if private_key else None client_cert = (cert.path, key.path) if cert and key else None demisto.debug(f"Command being called is {command}") try: client = Client( base_url=base_url, authorization=params["credentials"]["password"], verify=insecure, proxy=proxy, timeout=timeout, performance=performance, max_indicator_to_fetch=max_indicator_to_fetch, client_cert=client_cert, ) if command == "test-module": return_results(test_module(client)) elif command == "misp-feed-get-indicators": return_results(get_attributes_command(client, args, params)) elif command == "fetch-indicators": fetch_attributes_command(client, params) else: raise NotImplementedError(f"Command {command} is not implemented.") except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{e!s}") if __name__ in ("__main__", "builtin", "builtins"): # pragma: no cover main()