diff --git a/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2.py b/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2.py index 20a557af32de..dfd3d3c641d9 100644 --- a/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2.py +++ b/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2.py @@ -2,10 +2,13 @@ ''' IMPORTS ''' import requests -from datetime import datetime, timezone +from datetime import datetime +import pytz import urllib3 import json +UTC = pytz.UTC + # Disable insecure warnings urllib3.disable_warnings() @@ -113,9 +116,9 @@ def validate_input(args, is_iocs=False): _start_date = datetime(1, 1, 1, 0, 0) _end_date = datetime(1, 1, 1, 0, 0) - if limit <= 0 or limit > 1000: + if limit <= 0 or limit > 100: raise ValueError( - f"The limit argument should contain a positive number, up to 1000, limit: {limit}") + f"The limit argument should contain a positive number, up to 100, limit: {limit}") if _start_date > datetime.utcnow(): raise ValueError( @@ -132,10 +135,10 @@ def validate_input(args, is_iocs=False): if limit <= 0 or limit > LIMIT_EVENT_ITEMS: raise ValueError(f"The limit argument should contain a positive number, up to 1000, limit: {limit}") - if _start_date > datetime.now(tz=timezone.utc): + if _start_date > datetime.now(tz=UTC): raise ValueError( - f"Start date must be a date before or equal to {datetime.now(tz=timezone.utc).strftime(date_format)}") - if _end_date > datetime.now(tz=timezone.utc): + f"Start date must be a date before or equal to {datetime.now(tz=UTC).strftime(date_format)}") + if _end_date > datetime.now(tz=UTC): raise ValueError( f"End date must be a date before or equal to {args.get('end_date')}") if _start_date > _end_date: @@ -229,6 +232,11 @@ def format_incidents(alerts, hide_cvv_expiry): alert['data_message']['data']['bank']['card']['cvv'] = "xxx" alert['data_message']['data']['bank']['card']['expiry'] = "xx/xx/xxxx" + keyword = "" + if alert.get('metadata') and alert['metadata'].get('entity'): + if alert['metadata']['entity'].get('keyword') and alert['metadata']['entity']['keyword']['tag_name']: + keyword = alert['metadata']['entity']['keyword']['tag_name'] + alert_details = { "name": "Cyble Vision Alert on {}".format(alert['service']), "event_type": "{}".format(alert['service']), @@ -236,7 +244,7 @@ def format_incidents(alerts, hide_cvv_expiry): "alert_group_id": "{}".format(alert['alert_group_id']), "event_id": "{}".format(alert['id']), "data_message": json.dumps(alert['data_message']), - "keyword": "{}".format(alert['metadata']['entity']['keyword']['tag_name']), + "keyword": "{}".format(keyword), "created_at": "{}".format(alert['created_at']), "status": "{}".format(alert['status']), "mirrorInstance": demisto.integrationInstance() @@ -710,32 +718,64 @@ def cyble_fetch_iocs(client, method, token, args, url): if args.get('end_date'): input_params_alerts_iocs['endDate'] = args.get('end_date') - iocs = set_request(client, method, token, input_params_alerts_iocs, url) + response = set_request(client, method, token, input_params_alerts_iocs, url) try: lst_iocs = [] - for ioc in iocs['result']: + for ioc in response['iocs']: + + sources = [] + behaviour_tags = [] + target_countries = [] + target_regions = [] + target_industries = [] + related_malwares = [] + related_threat_actors = [] + + if ioc.get('sources'): + for source in ioc.get('sources'): + sources.append(source) + + if ioc.get('behaviour_tags'): + for behaviour_tag in ioc.get('behaviour_tags'): + behaviour_tags.append(behaviour_tag) + + if ioc.get('target_countries'): + for target_country in ioc.get('target_countries'): + target_countries.append(target_country) + + if ioc.get('target_regions'): + for target_region in ioc.get('target_regions'): + target_regions.append(target_region) - lst_attack = [] - lst_tags = [] + if ioc.get('target_industries'): + for target_industry in ioc.get('target_industries'): + target_industries.append(target_industry) - for attack_details in ioc['attack_id']: - lst_attack.append(attack_details['attack_id']) + if ioc.get('related_malware'): + for related_malware in ioc.get('related_malware'): + related_malwares.append(related_malware) - for ioc_tags in ioc['ioc_tags']: - lst_tags.append(ioc_tags['name']) + if ioc.get('related_threat_actors'): + for related_threat_actor in ioc.get('related_threat_actors'): + related_threat_actors.append(related_threat_actor) lst_iocs.append({'ioc': "{}".format(ioc['ioc']), + 'ioc_type': "{}".format(ioc['ioc_type']), 'first_seen': "{}".format(ioc['first_seen']), 'last_seen': "{}".format(ioc['last_seen']), - 'risk_rating': "{}".format(ioc['risk_rating']), - 'confident_rating': "{}".format(ioc['confident_rating']), - 'ioc_type': "{}".format(ioc['ioc_type']['name']), - 'attack': f"{lst_attack}", - 'tags': f"{lst_tags}" + 'risk_score': "{}".format(ioc['risk_score']), + 'confidence_rating': "{}".format(ioc['confidence_rating']), + 'sources': f"{sources}", + 'behaviour_tags': f"{behaviour_tags}", + 'target_countries': f"{target_countries}", + 'target_regions': f"{target_regions}", + 'target_industries': f"{target_industries}", + 'related_malware': f"{related_malwares}", + 'related_threat_actors': f"{related_threat_actors}", }) except Exception as e: - raise Exception(f"Error: [{e}] for response [{iocs}]") + raise Exception(f"Error: [{e}] for response [{response}]") markdown = tableToMarkdown('Indicator of Compromise:', lst_iocs, ) diff --git a/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2.yml b/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2.yml index 81686614a9b6..ab57d2a23609 100644 --- a/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2.yml +++ b/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2.yml @@ -103,11 +103,11 @@ script: - Emai - description: Returns records for the specified indicator value. name: ioc - - defaultValue: '0' + - defaultValue: '1' description: Returns records that starts from the given page number (the value of the form parameter) in the results list. name: from - defaultValue: '1' - description: Number of records to return (max 1000). Using a smaller limit will get faster responses. + description: Number of records to return (max 100). Using a smaller limit will get faster responses. name: limit - auto: PREDEFINED defaultValue: last_seen @@ -202,7 +202,7 @@ script: type: String - description: Retrieves a User Profile schema, which holds all of the user fields within the application. Used for outgoing-mapping through the Get Schema option. name: get-mapping-fields - dockerimage: demisto/python3:3.10.13.80014 + dockerimage: demisto/python3:3.11.9.107902 isfetch: true runonce: false script: '-' diff --git a/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2_test.py b/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2_test.py index 6eb842a32e62..7552c4259308 100644 --- a/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2_test.py +++ b/Packs/CybleEventsV2/Integrations/CybleEventsV2/CybleEventsV2_test.py @@ -11,12 +11,15 @@ """ import json -from datetime import datetime, timezone, timedelta +from datetime import datetime, timedelta +import pytz import pytest import demistomock as demisto +UTC = pytz.UTC + def util_load_json(path): with open("test_data/" + path, encoding='utf-8') as f: @@ -120,10 +123,10 @@ def test_get_iocs(requests_mock, offset): assert isinstance(response, list) assert isinstance(response[0], dict) assert response[0]['ioc'] == 'Indicator' - assert response[0]['last_seen'] == '2023-02-20 21:00:55' - assert response[0]['first_seen'] == '2023-02-20 21:00:55' - assert response[0]['ioc_type'] == 'some indicator type' - assert response[0]['risk_rating'] == 'Some Risk Rating' + assert response[0]['ioc_type'] == 'Some IOC Type' + assert response[0]['first_seen'] == '1722227702' + assert response[0]['last_seen'] == '1722472568' + assert response[0]['risk_score'] == '70' def test_get_alert_group(requests_mock): @@ -238,8 +241,8 @@ def test_limit_validate_input(capfd): from CybleEventsV2 import validate_input args = { - 'start_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z"), - 'end_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'start_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'end_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z"), 'from': '0', 'limit': '-1', } @@ -260,7 +263,7 @@ def test_limit_validate_ioc_input(capfd): } with capfd.disabled(), pytest.raises(ValueError, match="The limit argument should contain a positive number," - f" up to 1000, limit: {args.get('limit', '50')}"): + f" up to 100, limit: {args.get('limit', '50')}"): validate_input(args=args, is_iocs=True) @@ -284,8 +287,8 @@ def test_edate_validate_input(capfd): from CybleEventsV2 import validate_input args = { - 'start_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z"), - 'end_date': (datetime.now(tz=timezone.utc) + timedelta(days=4)).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'start_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'end_date': (datetime.now(tz=UTC) + timedelta(days=4)).strftime("%Y-%m-%dT%H:%M:%S%z"), 'from': '0', 'limit': '1' } @@ -300,8 +303,8 @@ def test_date_validate_input(capfd): from CybleEventsV2 import validate_input args = { - 'start_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z"), - 'end_date': (datetime.now(tz=timezone.utc) - timedelta(days=4)).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'start_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'end_date': (datetime.now(tz=UTC) - timedelta(days=4)).strftime("%Y-%m-%dT%H:%M:%S%z"), 'from': '0', 'limit': '1' } @@ -337,8 +340,8 @@ def test_offset_cyble_vision_fetch_detail(requests_mock, capfd): args = { 'from': '-1', 'limit': 1, - 'start_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z"), - 'end_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z") + 'start_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'end_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z") } url = "https://test.com/apollo/api/v1/y/alerts" @@ -368,8 +371,8 @@ def test_get_alert_fetch(requests_mock): args = { 'from': 1, 'limit': 1, - 'start_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z"), - 'end_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z") + 'start_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'end_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z") } url = "https://test.com/apollo/api/v1/y/alerts" @@ -400,8 +403,8 @@ def test_get_alert_fetch2(requests_mock): args = { 'from': 1, 'limit': 1, - 'start_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z"), - 'end_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z") + 'start_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z"), + 'end_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S%z") } url = "https://test.com/apollo/api/v1/y/alerts" @@ -451,8 +454,8 @@ def test_data_alert_invalidate_date(capfd): from CybleEventsV2 import validate_input args = { - 'start_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M"), - 'end_date': (datetime.now(tz=timezone.utc) - timedelta(days=4)).strftime("%Y-%m-%dT%H:%M"), + 'start_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M"), + 'end_date': (datetime.now(tz=UTC) - timedelta(days=4)).strftime("%Y-%m-%dT%H:%M"), 'from': '0', 'limit': '1' } @@ -467,8 +470,8 @@ def test_data_alert_iocs_date(capfd): from CybleEventsV2 import validate_input args = { - 'start_date': datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M"), - 'end_date': (datetime.now(tz=timezone.utc) - timedelta(days=4)).strftime("%Y-%m-%dT%H:%M"), + 'start_date': datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M"), + 'end_date': (datetime.now(tz=UTC) - timedelta(days=4)).strftime("%Y-%m-%dT%H:%M"), 'from': '0', 'limit': '1' } diff --git a/Packs/CybleEventsV2/Integrations/CybleEventsV2/README.md b/Packs/CybleEventsV2/Integrations/CybleEventsV2/README.md index d90ec4d78933..2900406b6b38 100644 --- a/Packs/CybleEventsV2/Integrations/CybleEventsV2/README.md +++ b/Packs/CybleEventsV2/Integrations/CybleEventsV2/README.md @@ -60,17 +60,17 @@ Fetch the indicators in the given timeline. #### Input -| **Argument Name** | **Description** | **Required** | -|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------| -| ioc_type | Returns records according to their type (Domain, FileHash-MD5, FileHash-SHA1, FileHash-SHA256, IPv4, IPv6, URL, Email). Default is Domain. | Optional | -| ioc | Returns records for the specified indicator value. | Optional | -| from | Returns records that starts from the given page number (the value of the form parameter) in the results list. Default is 0. | Optional | -| limit | Number of records to return (max 1000). Using a smaller limit will get faster responses. Default is 1. | Optional | -| sort_by | Sorting based on the column(last_seen,first_seen,ioc_type). Possible values are: last_seen, first_seen, ioc_type. Default is last_seen. | Optional | -| order | Sorting order for ioc either Ascending or Descending based on sort by. Default is desc. | Optional | -| tags | Returns records for the specified tags. | Optional | -| start_date | Timeline start date in the format "YYYY-MM-DD". Should be used with start_date as timeline range. | Optional | -| end_date | Timeline end date in the format "YYYY-MM-DD". Should be used with end_date as timeline range. | Optional | +| **Argument Name** | **Description** | **Required** | +|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| +| ioc_type | Returns records according to their type (Domain, FileHash-MD5, FileHash-SHA1, FileHash-SHA256, IPv4, IPv6, URL, Email, Wallet-Address). Default is Domain. | Optional | +| ioc | Returns records for the specified indicator value. | Optional | +| from | Returns records that starts from the given page number (the value of the form parameter) in the results list. Default is 1. | Optional | +| limit | Number of records to return (max 100). Using a smaller limit will get faster responses. Default is 1. | Optional | +| sort_by | Sorting based on the column(last_seen,first_seen,ioc_type). Possible values are: last_seen, first_seen, ioc_type. Default is last_seen. | Optional | +| order | Sorting order for ioc either Ascending or Descending based on sort by. Default is desc. | Optional | +| tags | Returns records for the specified tags. | Optional | +| start_date | Timeline start date in the format "YYYY-MM-DD". Should be used with start_date as timeline range. | Optional | +| end_date | Timeline end date in the format "YYYY-MM-DD". Should be used with end_date as timeline range. | Optional | #### Context Output diff --git a/Packs/CybleEventsV2/Integrations/CybleEventsV2/test_data/dummy_fetch_iocs.json b/Packs/CybleEventsV2/Integrations/CybleEventsV2/test_data/dummy_fetch_iocs.json index a3031900a29d..2fb727f4442a 100644 --- a/Packs/CybleEventsV2/Integrations/CybleEventsV2/test_data/dummy_fetch_iocs.json +++ b/Packs/CybleEventsV2/Integrations/CybleEventsV2/test_data/dummy_fetch_iocs.json @@ -1,64 +1,40 @@ { "data": { "pagination": { - "count": 17381117, - "items_per_page": 10, - "page": 1 + "total_count": 17381117, + "page": 1, + "limit": 10 }, - "result": [ - { - "asn_id": null, - "attack_id": [ - { - "created_at": "2023-01-05T04:09:37.034894Z", - "attack_id": "T1071.001", - "name": "sample protocol name", - "updated_at": "2023-01-05T04:09:37.034894Z", - "uuid": "00000000-0000-0000-0000-000000000000", - "pivot": { - "ioc_id": "00000000-0000-0000-0000-000000000000", - "ioc_attack_id": "8992fe13-010f-5863-b025-6c15c07d00a7" - } - } - ], - "confident_rating": "Some confident Rating", - "created_at": "2023-02-21T02:51:38.959289Z", - "created_by": null, - "first_seen": "2023-02-20 21:00:55", - "ioc": "Indicator", - "ioc_type": { - "created_at": "2023-01-05T03:00:46.918397Z", - "created_by": null, - "updated_at": "2023-01-05T03:00:46.918397Z", - "uuid": "00000000-0000-0000-0000-000000000000", - "name": "some indicator type" - }, - "ioc_tags": [ - { - "created_at": "2023-02-12T16:57:07.204597Z", - "name": "Tag Name", - "pivot": { - "ioc_id": "00000000-0000-0000-0000-000000000000", - "tag_id": "00000000-0000-0000-0000-000000000000" - }, - "tag_type_id": "00000000-0000-0000-0000-000000000000", - "updated_at": "2023-02-12T16:57:07.204597Z", - "uuid": "00000000-0000-0000-0000-000000000000" - } - ], - "iocs_source_name": { - "created_at": "2023-02-10T06:20:56.328463Z", - "name": "Feed Name", - "updated_at": "2023-02-10T06:20:56.328463Z", - "uuid": "00000000-0000-0000-0000-000000000000" - }, - "last_seen": "2023-02-20 21:00:55", - "reference_link": null, - "risk_rating": "Some Risk Rating", - "source_name_id": "00000000-0000-0000-0000-000000000000", - "updated_at": "2023-02-21T02:51:38.959289Z", - "uuid": "00000000-0000-0000-0000-000000000000" - } + "iocs": [ + { + "ioc": "Indicator", + "ioc_type": "Some IOC Type", + "first_seen": 1722227702, + "last_seen": 1722472568, + "risk_score": 70, + "sources": [ + "Some Source" + ], + "behaviour_tags": [ + "Some Tags" + ], + "confidence_rating": "Some Rating", + "target_countries": [ + "Some Countries" + ], + "target_regions": [ + "Some Regions" + ], + "target_industries": [ + "Some Industries" + ], + "related_malware": [ + "Some Malware" + ], + "related_threat_actors": [ + "Some Actors" + ] + } ] }, "success": true diff --git a/Packs/CybleEventsV2/ReleaseNotes/1_0_4.md b/Packs/CybleEventsV2/ReleaseNotes/1_0_4.md new file mode 100644 index 000000000000..5434b5c013a6 --- /dev/null +++ b/Packs/CybleEventsV2/ReleaseNotes/1_0_4.md @@ -0,0 +1,9 @@ +#### Integrations + +##### CybleEvents v2 + +- Updated the Docker image to: *demisto/python3:3.11.9.107902*. +- Updated app code to incorporate IOC changes +- New max IOC limit is 100 +- Added new IOC Type: Wallet-Address +- Big fixes in Alerts fetching code diff --git a/Packs/CybleEventsV2/pack_metadata.json b/Packs/CybleEventsV2/pack_metadata.json index 087f671ec191..9744e6ea9ed6 100644 --- a/Packs/CybleEventsV2/pack_metadata.json +++ b/Packs/CybleEventsV2/pack_metadata.json @@ -2,7 +2,7 @@ "name": "CybleEventsV2", "description": "Cyble Events for Vision Users. Must have Vision API access to use the threat intelligence.", "support": "partner", - "currentVersion": "1.0.3", + "currentVersion": "1.0.4", "author": "Cyble Info Sec", "url": "https://cyble.com/", "email": "",