diff --git a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence.py b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence.py index 236c0541c45d..ace61e2313fa 100644 --- a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence.py +++ b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence.py @@ -170,6 +170,17 @@ def url(self, url: str, relationships: str = ''): ok_codes=(404, 429, 200) ) + def private_url(self, url: str): + """ + See Also: + https://gtidocs.virustotal.com/reference/get-a-private-url-analysis-report + """ + return self._http_request( + 'GET', + f'private/urls/{encode_url_to_base64(url)}', + ok_codes=(404, 429, 200) + ) + def domain(self, domain: str, relationships: str = '') -> dict: """ See Also: @@ -362,7 +373,7 @@ def private_file_scan(self, file_path: str) -> dict: resp_type='response' ) demisto.debug( - f'scan_file response:\n' + f'scan_private_file response:\n' f'{str(response.status_code)=}, {str(response.headers)=}, {str(response.content)}' ) return response.json() @@ -398,6 +409,17 @@ def url_scan(self, url: str) -> dict: data={'url': url} ) + def private_url_scan(self, url: str) -> dict: + """ + See Also: + https://gtidocs.virustotal.com/reference/private-scan-url + """ + return self._http_request( + 'POST', + '/private/urls', + data={'url': url} + ) + # endregion def file_sandbox_report(self, file_hash: dict, limit: int) -> dict: @@ -454,14 +476,14 @@ def get_private_analysis(self, analysis_id: str) -> dict: f'private/analyses/{analysis_id}' ) - def get_private_file_from_analysis(self, analysis_id: str) -> dict: + def get_private_item_from_analysis(self, analysis_id: str) -> dict: """ See Also: https://gtidocs.virustotal.com/reference/analysesidrelationship """ return self._http_request( 'GET', - f'private/analyses/{analysis_id}/item?attributes=threat_severity,threat_verdict' + f'private/analyses/{analysis_id}/item' ) def get_file_sigma_analysis(self, file_hash: str) -> dict: @@ -1410,6 +1432,37 @@ def build_url_output( ) +def build_private_url_output(url: str, raw_response: dict) -> CommandResults: + data = raw_response.get('data', {}) + attributes = data.get('attributes', {}) + + last_analysis_stats = attributes.get('last_analysis_stats', {}) + positive_detections = last_analysis_stats.get('malicious', 0) + detection_engines = sum(last_analysis_stats.values()) + + return CommandResults( + outputs_prefix=f'{INTEGRATION_ENTRY_CONTEXT}.URL', + outputs_key_field='id', + readable_output=tableToMarkdown( + f'URL data of "{url}"', + { + **attributes, + 'positives': f'{positive_detections}/{detection_engines}', + }, + headers=[ + 'url', + 'title', + 'last_http_response_content_sha256', + 'positives', + ], + removeNull=True, + headerTransform=string_to_table_header + ), + outputs=data, + raw_response=raw_response, + ) + + def build_ip_output( client: Client, score_calculator: ScoreCalculator, @@ -1784,7 +1837,7 @@ def private_file_command(client: Client, args: dict) -> List[CommandResults]: execution_metrics.success += 1 except Exception as exc: # If anything happens, just keep going - demisto.debug(f'Could not process file: "{file}"\n {str(exc)}') + demisto.debug(f'Could not process private file: "{file}"\n {str(exc)}') execution_metrics.general_error += 1 results.append(build_error_file_output(client, file)) continue @@ -1803,6 +1856,7 @@ def url_command(client: Client, score_calculator: ScoreCalculator, args: dict, r extended_data = argToBoolean(args.get('extended_data', False)) results: List[CommandResults] = [] execution_metrics = ExecutionMetrics() + for url in urls: try: raw_response = client.url(url, relationships) @@ -1828,6 +1882,39 @@ def url_command(client: Client, score_calculator: ScoreCalculator, args: dict, r return results +def private_url_command(client: Client, args: dict) -> List[CommandResults]: + """ + 1 API Call + """ + urls = argToList(args['url']) + results: List[CommandResults] = [] + execution_metrics = ExecutionMetrics() + + for url in urls: + try: + raw_response = client.private_url(url) + if raw_response.get('error', {}).get('code') == 'QuotaExceededError': + execution_metrics.quota_error += 1 + results.append(build_quota_exceeded_url_output(client, url)) + continue + if raw_response.get('error', {}).get('code') == 'NotFoundError': + results.append(build_unknown_url_output(client, url)) + continue + except Exception as exc: + # If anything happens, just keep going + demisto.debug(f'Could not process private URL: "{url}".\n {str(exc)}') + execution_metrics.general_error += 1 + results.append(build_error_url_output(client, url)) + continue + execution_metrics.success += 1 + results.append(build_private_url_output(url, raw_response)) + if execution_metrics.is_supported(): + _metric_results = execution_metrics.metrics + metric_results = cast(CommandResults, _metric_results) + results.append(metric_results) + return results + + def domain_command(client: Client, score_calculator: ScoreCalculator, args: dict, relationships: str) -> List[CommandResults]: """ 1 API Call for regular @@ -2031,7 +2118,6 @@ def file_scan_and_get_analysis( def private_file_scan_and_get_analysis(client: Client, args: dict): """Calls to gti-privatescanning-file-scan and gti-privatescanning-analysis-get.""" interval = int(args.get('interval_in_seconds', 60)) - extended = argToBoolean(args.get('extended_data', False)) if not args.get('id'): command_results = private_file_scan(client, args) @@ -2046,7 +2132,6 @@ def private_file_scan_and_get_analysis(client: Client, args: dict): 'entryID': args.get('entryID'), 'id': outputs.get('vtScanID'), 'interval_in_seconds': interval, - 'extended_data': extended, }, timeout_in_seconds=6000, ) @@ -2066,7 +2151,6 @@ def private_file_scan_and_get_analysis(client: Client, args: dict): 'entryID': args.get('entryID'), 'id': outputs.get('id'), 'interval_in_seconds': interval, - 'extended_data': extended, }, timeout_in_seconds=6000, ) @@ -2122,6 +2206,47 @@ def url_scan_and_get_analysis( return CommandResults(scheduled_command=scheduled_command) +def private_url_scan_and_get_analysis(client: Client, args: dict): + """Calls to gti-privatescanning-url-scan and gti-privatescanning-analysis-get.""" + interval = int(args.get('interval_in_seconds', 60)) + + if not args.get('id'): + command_result = private_scan_url_command(client, args) + outputs = command_result.outputs + if not isinstance(outputs, dict): + raise DemistoException('outputs is expected to be a dict') + scheduled_command = ScheduledCommand( + command=f'{COMMAND_PREFIX}-private-url-scan-and-analysis-get', + next_run_in_seconds=interval, + args={ + 'url': args.get('url'), + 'id': outputs.get('vtScanID'), + 'interval_in_seconds': interval, + }, + timeout_in_seconds=6000, + ) + command_result.scheduled_command = scheduled_command + return command_result + + command_result = private_get_analysis_command(client, args) + outputs = command_result.outputs + if not isinstance(outputs, dict): + raise DemistoException('outputs is expected to be a dict') + if outputs.get('data', {}).get('attributes', {}).get('status') == 'completed': + return command_result + scheduled_command = ScheduledCommand( + command=f'{COMMAND_PREFIX}-private-url-scan-and-analysis-get', + next_run_in_seconds=interval, + args={ + 'url': args.get('url'), + 'id': outputs.get('id'), + 'interval_in_seconds': interval, + }, + timeout_in_seconds=6000, + ) + return CommandResults(scheduled_command=scheduled_command) + + def get_upload_url(client: Client) -> CommandResults: """ 1 API Call @@ -2143,6 +2268,20 @@ def get_upload_url(client: Client) -> CommandResults: def scan_url_command(client: Client, args: dict) -> CommandResults: + """ + 1 API Call + """ + return scan_url(client, args) + + +def private_scan_url_command(client: Client, args: dict) -> CommandResults: + """ + 1 API Call + """ + return scan_url(client, args, True) + + +def scan_url(client: Client, args: dict, private: bool = False) -> CommandResults: """ 1 API Call """ @@ -2153,7 +2292,10 @@ def scan_url_command(client: Client, args: dict) -> CommandResults: headers = ['id', 'url'] try: - raw_response = client.url_scan(url) + if private: + raw_response = client.private_url_scan(url) + else: + raw_response = client.url_scan(url) data = raw_response['data'] data['url'] = url @@ -2507,22 +2649,38 @@ def private_get_analysis_command(client: Client, args: dict) -> CommandResults: raw_response = client.get_private_analysis(analysis_id) data = raw_response.get('data', {}) attributes = data.get('attributes', {}) - stats = { - 'threat_severity_level': '', - 'popular_threat_category': '', - 'threat_verdict': '', - } + + if sha256 := raw_response.get('meta', {}).get('file_info', {}).get('sha256'): + attributes['sha256'] = sha256 + + if url := raw_response.get('meta', {}).get('url_info', {}).get('url'): + attributes['url'] = url + if attributes.get('status', '') == 'completed': - file_response = client.get_private_file_from_analysis(analysis_id) - file_attributes = file_response.get('data', {}).get('attributes', {}) - threat_severity = file_attributes.get('threat_severity', {}) - severity_level = threat_severity.get('threat_severity_level', '') - stats['threat_severity_level'] = SEVERITY_LEVELS.get(severity_level, severity_level) - threat_severity_data = threat_severity.get('threat_severity_data', {}) - stats['popular_threat_category'] = threat_severity_data.get('popular_threat_category', '') - verdict = file_attributes.get('threat_verdict', '') - stats['threat_verdict'] = VERDICTS.get(verdict, verdict) - attributes.update(stats) + stats = {} + item_response = client.get_private_item_from_analysis(analysis_id) + item_attributes = item_response.get('data', {}).get('attributes', {}) + + # File attributes + if threat_severity := item_attributes.get('threat_severity'): + if severity_level := threat_severity.get('threat_severity_level'): + stats['threat_severity_level'] = SEVERITY_LEVELS.get(severity_level, severity_level) + if popular_threat_category := threat_severity.get('threat_severity_data', {}).get('popular_threat_category'): + stats['popular_threat_category'] = popular_threat_category + if verdict := item_attributes.get('threat_verdict'): + stats['threat_verdict'] = VERDICTS.get(verdict, verdict) + + # URL attributes + if last_analysis_stats := item_attributes.get('last_analysis_stats'): + if detection_engines := sum(last_analysis_stats.values()): + positive_detections = last_analysis_stats.get('malicious', 0) + stats['positives'] = f'{positive_detections}/{detection_engines}' + + attributes.update(stats) + for field in ['title', 'last_http_response_content_sha256']: + if value := item_attributes.get(field): + attributes[field] = value + return CommandResults( f'{INTEGRATION_ENTRY_CONTEXT}.Analysis', 'id', @@ -2532,7 +2690,21 @@ def private_get_analysis_command(client: Client, args: dict) -> CommandResults: **attributes, 'id': analysis_id }, - headers=['id', 'threat_severity_level', 'popular_threat_category', 'threat_verdict', 'status'], + headers=[ + # Common headers + 'id', + 'status', + # File attributes + 'sha256' + 'threat_severity_level', + 'popular_threat_category', + 'threat_verdict', + # URL attributes + 'url', + 'title', + 'last_http_response_content_sha256', + 'positives', + ], removeNull=True, headerTransform=string_to_table_header ), @@ -2794,6 +2966,10 @@ def main(params: dict, args: dict, command: str): results = private_file_command(client, args) elif command == f'{COMMAND_PREFIX}-privatescanning-file-scan': results = private_file_scan(client, args) + elif command == f'{COMMAND_PREFIX}-privatescanning-url': + results = private_url_command(client, args) + elif command == f'{COMMAND_PREFIX}-privatescanning-url-scan': + results = private_scan_url_command(client, args) elif command == f'{COMMAND_PREFIX}-privatescanning-analysis-get': results = private_get_analysis_command(client, args) elif command == f'{COMMAND_PREFIX}-assessment-get': @@ -2804,6 +2980,8 @@ def main(params: dict, args: dict, command: str): results = private_file_scan_and_get_analysis(client, args) elif command == f'{COMMAND_PREFIX}-url-scan-and-analysis-get': results = url_scan_and_get_analysis(client, score_calculator, args, url_relationships) + elif command == f'{COMMAND_PREFIX}-private-url-scan-and-analysis-get': + results = private_url_scan_and_get_analysis(client, args) elif command == f'{COMMAND_PREFIX}-curated-campaigns-get': results = get_curated_campaigns_command(client, args) elif command == f'{COMMAND_PREFIX}-curated-malware-families-get': diff --git a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence.yml b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence.yml index 082b8ffe58c7..1f6b17491dba 100644 --- a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence.yml +++ b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence.yml @@ -5,6 +5,9 @@ name: GoogleThreatIntelligence display: Google Threat Intelligence fromversion: 6.10.0 category: Data Enrichment & Threat Intelligence +sectionOrder: +- Connect +- Collect description: Analyzes suspicious hashes, URLs, domains, and IP addresses. configuration: - display: API Key (leave empty. Fill in the API key in the password field.) @@ -13,6 +16,7 @@ configuration: type: 9 required: true hiddenusername: true + section: Connect - additionalinfo: Reliability of the source providing the intelligence data defaultvalue: C - Fairly reliable display: Source Reliability @@ -26,92 +30,110 @@ configuration: - E - Unreliable - F - Reliability cannot be judged required: false + section: Collect - display: GTI Malicious Verdict. Check Google Threat Intelligence verdict to consider the file malicious. name: gti_malicious defaultvalue: 'false' type: 8 required: false + section: Collect - display: GTI Suspicious Verdict. Check Google Threat Intelligence verdict to consider the file suspicious. name: gti_suspicious defaultvalue: 'false' type: 8 required: false + section: Collect - display: File Malicious Threshold. Minimum number of positive results from VT scanners to consider the file malicious. name: fileThreshold defaultvalue: '10' type: 0 required: false + section: Collect - display: File Suspicious Threshold. Minimum number of positive and suspicious results from VT scanners to consider the file suspicious. name: fileSuspiciousThreshold defaultvalue: '5' type: 0 required: false + section: Collect - display: IP Malicious Threshold. Minimum number of positive results from VT scanners to consider the IP malicious. name: ipThreshold defaultvalue: '10' type: 0 required: false + section: Collect - display: IP Suspicious Threshold. Minimum number of positive and suspicious results from VT scanners to consider the IP suspicious. name: ipSuspiciousThreshold defaultvalue: '5' type: 0 required: false + section: Collect - display: Disable reputation lookups for private IP addresses name: disable_private_ip_lookup defaultvalue: 'false' type: 8 required: false additionalinfo: To reduce the number of lookups made to the VT API, this option can be selected to gracefully skip enrichment of any IP addresses allocated for private networks. + section: Collect - display: 'URL Malicious Threshold. Minimum number of positive results from VT scanners to consider the URL malicious.' name: urlThreshold defaultvalue: '10' type: 0 required: false + section: Collect - display: 'URL Suspicious Threshold. Minimum number of positive and suspicious results from VT scanners to consider the URL suspicious.' name: urlSuspiciousThreshold defaultvalue: '5' type: 0 required: false + section: Collect - display: Domain Malicious Threshold. Minimum number of positive results from VT scanners to consider the Domain malicious. name: domainThreshold defaultvalue: '10' type: 0 required: false + section: Collect - display: Domain Suspicious Threshold. Minimum number of positive and suspicious results from VT scanners to consider the Domain suspicious. name: domainSuspiciousThreshold defaultvalue: '5' type: 0 required: false -- display: 'Preferred Vendors List. CSV list of vendors who are considered more trustworthy.' + section: Collect +- display: Preferred Vendors List. CSV list of vendors who are considered more trustworthy. name: preferredVendors defaultvalue: '' type: 12 required: false -- display: 'Preferred Vendor Threshold. The minimum number of highly trusted vendors required to consider a domain, IP address, URL, or file as malicious. ' + section: Collect +- display: Preferred Vendor Threshold. The minimum number of highly trusted vendors required to consider a domain, IP address, URL, or file as malicious. name: preferredVendorsThreshold defaultvalue: '5' type: 0 required: false -- display: 'Enable score analyzing by Crowdsourced Yara Rules, Sigma, and IDS' + section: Collect +- display: Enable score analyzing by Crowdsourced Yara Rules, Sigma, and IDS. name: crowdsourced_yara_rules_enabled type: 8 defaultvalue: 'true' required: false + section: Collect - display: Crowdsourced Yara Rules Threshold name: yaraRulesThreshold type: 0 defaultvalue: '1' required: false + section: Collect - display: Sigma and Intrusion Detection Rules Threshold name: SigmaIDSThreshold type: 0 defaultvalue: '5' required: false -- display: 'Domain Popularity Ranking Threshold' + section: Collect +- display: Domain Popularity Ranking Threshold name: domain_popularity_ranking type: 0 defaultvalue: '10000' required: false + section: Collect - display: IP Relationships name: ip_relationships type: 16 @@ -123,6 +145,7 @@ configuration: - referrer files - 'urls' defaultvalue: 'communicating files,downloaded files,referrer files,urls' + section: Collect - additionalinfo: Select the list of relationships to retrieve from the API. display: Domain Relationships name: domain_relationships @@ -143,6 +166,7 @@ configuration: type: 16 defaultvalue: 'cname records,caa records,communicating files,downloaded files,immediate parent,mx records,ns records,parent,referrer files,siblings,soa records,subdomains,urls' required: false + section: Collect - additionalinfo: Select the list of relationships to retrieve from the API. display: URL Relationships name: url_relationships @@ -160,6 +184,7 @@ configuration: type: 16 defaultvalue: 'communicating files,contacted domains,contacted ips,downloaded files,last serving ip address,network location,redirecting urls,redirects to,referrer files,referrer urls' required: false + section: Collect - display: File Relationships name: file_relationships type: 16 @@ -189,14 +214,17 @@ configuration: - pe resource parents - 'similar files' defaultvalue: 'carbonblack children,carbonblack parents,compressed parents,contacted domains,contacted ips,contacted urls,dropped files,email attachments,email parents,embedded domains,embedded ips,embedded urls,execution parents,itw domains,itw ips,overlay children,overlay parents,pcap children,pcap parents,pe resource children,pe resource parents,similar files' + section: Collect - display: Use system proxy settings name: proxy type: 8 required: false + section: Connect - display: Trust any certificate (not secure) name: insecure type: 8 required: false + section: Connect script: script: '' type: python @@ -1679,8 +1707,94 @@ script: - contextPath: GoogleThreatIntelligence.Submission.Type description: Type of the file. type: String + - name: gti-privatescanning-url + description: Checks the reputation of a private URL. + arguments: + - name: url + required: true + default: true + description: Private URL to check. + isArray: true + outputs: + - contextPath: GoogleThreatIntelligence.URL.attributes.favicon.raw_md5 + description: The MD5 hash of the URL. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.favicon.dhash + description: Difference hash. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.last_http_response_content_length + description: The last HTTPS response length. + type: Number + - contextPath: GoogleThreatIntelligence.URL.attributes.last_http_response_headers.date + description: The last response header date. + type: Date + - contextPath: GoogleThreatIntelligence.URL.attributes.last_http_response_headers.x-sinkhole + description: DNS sinkhole from last response. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.last_http_response_headers.content-length + description: The content length of the last response. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.last_http_response_headers.content-type + description: The content type of the last response. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.last_http_response_content_sha256 + description: The SHA-256 hash of the content of the last response. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.last_http_response_code + description: Last response status code. + type: Number + - contextPath: GoogleThreatIntelligence.URL.attributes.last_final_url + description: Last final URL. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.url + description: The URL itself. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.title + description: Title of the page. + type: String + - contextPath: GoogleThreatIntelligence.URL.attributes.last_analysis_stats.harmless + description: The number of engines that found the domain to be harmless. + type: Number + - contextPath: GoogleThreatIntelligence.URL.attributes.last_analysis_stats.malicious + description: The number of engines that found the indicator to be malicious. + type: Number + - contextPath: GoogleThreatIntelligence.URL.attributes.last_analysis_stats.suspicious + description: The number of engines that found the indicator to be suspicious. + type: Number + - contextPath: GoogleThreatIntelligence.URL.attributes.last_analysis_stats.undetected + description: The number of engines that could not detect the indicator. + type: Number + - contextPath: GoogleThreatIntelligence.URL.attributes.last_analysis_stats.timeout + description: The number of engines that timed out for the indicator. + type: Number + - contextPath: GoogleThreatIntelligence.URL.attributes.outgoing_links + description: Outgoing links of the URL page. + type: String + - contextPath: GoogleThreatIntelligence.URL.type + description: Type of the indicator (private_url). + type: String + - contextPath: GoogleThreatIntelligence.URL.id + description: ID of the indicator. + type: String + - contextPath: GoogleThreatIntelligence.URL.links.self + description: Link to the response. + type: String + - name: gti-privatescanning-url-scan + description: Submits an URL for private scanning. Use the gti-privatescanning-analysis-get command to get the scan results. + arguments: + - name: url + required: true + default: true + description: The private URL to scan. + outputs: + - contextPath: GoogleThreatIntelligence.Submission.Type + description: The type of the submission (private_analysis). + type: String + - contextPath: GoogleThreatIntelligence.Submission.id + description: The ID of the submission. + type: String - name: gti-privatescanning-analysis-get - description: Get analysis of a private file submitted to GoogleThreatIntelligence. + description: Get analysis of a private file or URL submitted to GoogleThreatIntelligence. arguments: - name: id required: true @@ -1693,20 +1807,35 @@ script: - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.status description: Status of the analysis. type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.sha256 + description: SHA-256 hash of the private file. + type: String - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.threat_severity_level - description: Threat severity level of the private file. + description: Threat severity level of the private file (if analysis is completed). type: String - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.popular_threat_category - description: Popular threat category of the private file. + description: Popular threat category of the private file (if analysis is completed). type: String - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.threat_verdict - description: Threat verdict of the private file. + description: Threat verdict of the private file (if analysis is completed). + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.url + description: URL submitted. + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.title + description: Title of the private URL (if analysis is completed). + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.last_http_response_content_sha256 + description: Last HTTP response content SHA-256 hash of the private URL (if analysis is completed). + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.positives + description: Ratio of malicious detections to the total number of engines that scanned the private URL (if analysis is completed). type: String - contextPath: GoogleThreatIntelligence.Analysis.data.id description: ID of the analysis. type: String - contextPath: GoogleThreatIntelligence.Analysis.data.type - description: Type of object (analysis). + description: Type of object (private_analysis). type: String - contextPath: GoogleThreatIntelligence.Analysis.meta.file_info.sha256 description: SHA-256 hash of the file. @@ -1720,6 +1849,12 @@ script: - contextPath: GoogleThreatIntelligence.Analysis.meta.file_info.size description: Size of the file. type: Number + - contextPath: GoogleThreatIntelligence.Analysis.meta.url_info.id + description: ID of the URL. + type: String + - contextPath: GoogleThreatIntelligence.Analysis.meta.url_info.url + description: URL submitted. + type: String - contextPath: GoogleThreatIntelligence.Analysis.id description: The analysis ID. type: String @@ -2050,20 +2185,23 @@ script: - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.status description: Status of the analysis. type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.sha256 + description: SHA-256 hash of the private file. + type: String - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.threat_severity_level - description: Threat severity level of the private file. + description: Threat severity level of the private file (if analysis is completed). type: String - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.popular_threat_category - description: Popular threat category of the private file. + description: Popular threat category of the private file (if analysis is completed). type: String - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.threat_verdict - description: Threat verdict of the private file. + description: Threat verdict of the private file (if analysis is completed). type: String - contextPath: GoogleThreatIntelligence.Analysis.data.id description: ID of the analysis. type: String - contextPath: GoogleThreatIntelligence.Analysis.data.type - description: Type of object (analysis). + description: Type of object (private_analysis). type: String - contextPath: GoogleThreatIntelligence.Analysis.meta.file_info.sha256 description: SHA-256 hash of the file. @@ -2272,6 +2410,60 @@ script: - contextPath: GoogleThreatIntelligence.Analysis.id description: The analysis ID. type: String + - name: gti-private-url-scan-and-analysis-get + description: Scan and get the analysis of a private URL submitted to GoogleThreatIntelligence. + polling: true + arguments: + - name: url + required: true + default: true + description: The URL to scan. + - name: id + description: This is an internal argument used for the polling process, not to be used by the user. + - name: extended_data + description: Whether to return extended data. + defaultValue: false + auto: PREDEFINED + predefined: + - 'true' + - 'false' + - name: interval_in_seconds + description: Interval in seconds between each poll. + defaultValue: '60' + outputs: + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.date + description: Date of the analysis in epoch format. + type: Number + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.status + description: Status of the analysis. + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.url + description: URL submitted. + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.title + description: Title of the private URL (if analysis is completed). + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.last_http_response_content_sha256 + description: Last HTTP response content SHA-256 hash of the private URL (if analysis is completed). + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.attributes.positives + description: Ratio of malicious detections to the total number of engines that scanned the private URL (if analysis is completed). + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.id + description: ID of the analysis. + type: String + - contextPath: GoogleThreatIntelligence.Analysis.data.type + description: Type of object (private_analysis). + type: String + - contextPath: GoogleThreatIntelligence.Analysis.meta.url_info.id + description: ID of the URL. + type: String + - contextPath: GoogleThreatIntelligence.Analysis.meta.url_info.url + description: URL submitted. + type: String + - contextPath: GoogleThreatIntelligence.Analysis.id + description: The analysis ID. + type: String - name: gti-curated-campaigns-get description: Retrieves GTI curated campaigns for a given resource. arguments: @@ -2383,6 +2575,6 @@ script: - contextPath: GoogleThreatIntelligence.Collection.collections.attributes.targeted_industries description: Targeted industries of the curated threat actors. type: list - dockerimage: demisto/python3:3.11.10.115186 + dockerimage: demisto/python3:3.12.8.1983910 tests: - GoogleThreatIntelligence-test diff --git a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence_test.py b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence_test.py index 26b534fcf370..7d7f84e7f6db 100644 --- a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence_test.py +++ b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/GoogleThreatIntelligence_test.py @@ -732,6 +732,77 @@ def test_not_found_private_file_command(mocker, requests_mock): assert results[0].indicator.dbot_score.score == 0 +def test_private_url_command(mocker, requests_mock): + """ + Given: + - A valid Testing private URL + + When: + - Running the !gti-privatescanning-url command + + Then: + - Validate the command results are valid and contains metric data + """ + from GoogleThreatIntelligence import private_url_command, Client + import CommonServerPython + # Setup Mocks + url = 'https://www.example.com' + mocker.patch.object(demisto, 'args', return_value={'url': url}) + mocker.patch.object(demisto, 'params', return_value=DEFAULT_PARAMS) + mocker.patch.object(CommonServerPython, 'is_demisto_version_ge', return_value=True) + + # Assign arguments + params = demisto.params() + client = Client(params=params) + + # Load assertions and mocked request data + mock_response = util_load_json('test_data/private_url.json') + expected_results = util_load_json('test_data/private_url_results.json') + requests_mock.get(f'https://www.virustotal.com/api/v3/private/urls/{encode_url_to_base64(url)}', + json=mock_response) + + # Run command and collect result array + results = private_url_command(client=client, args=demisto.args()) + + assert results[1].execution_metrics == [{'APICallsCount': 1, 'Type': 'Successful'}] + assert results[0].execution_metrics is None + assert results[0].outputs == expected_results + + +def test_not_found_private_url_command(mocker, requests_mock): + """ + Given: + - A valid Testing private file + + When: + - Running the !gti-privatescanning-url command + + Then: + - Display "Not found" message to user + """ + from GoogleThreatIntelligence import private_url_command, Client + import CommonServerPython + # Setup Mocks + url = 'https://www.example.com' + mocker.patch.object(demisto, 'args', return_value={'url': url}) + mocker.patch.object(demisto, 'params', return_value=DEFAULT_PARAMS) + mocker.patch.object(CommonServerPython, 'is_demisto_version_ge', return_value=True) + + # Assign arguments + params = demisto.params() + client = Client(params=params) + + mock_response = {'error': {'code': 'NotFoundError'}} + requests_mock.get(f'https://www.virustotal.com/api/v3/private/urls/{encode_url_to_base64(url)}', + json=mock_response) + + results = private_url_command(client=client, args=demisto.args()) + + assert results[0].execution_metrics is None + assert results[0].readable_output == f'URL "{url}" was not found in GoogleThreatIntelligence.' + assert results[0].indicator.dbot_score.score == 0 + + def test_not_found_file_sandbox_report_command(mocker, requests_mock): """ Given: @@ -1029,11 +1100,6 @@ def test_gti_analysis_get(mocker, requests_mock): mock_response = { 'data': { 'attributes': { - 'stats': { - 'threat_severity_level': '', - 'popular_threat_category': '', - 'threat_verdict': '', - }, 'status': 'completed', } } @@ -1049,13 +1115,13 @@ def test_gti_analysis_get(mocker, requests_mock): assert results.outputs == {'id': 'random_id', **mock_response} -def test_gti_private_analysis_get(mocker, requests_mock): +def test_pending_gti_private_analysis_get(mocker, requests_mock): """ Given: - A valid analysis ID When: - - Running the !gti-privatescanning-analysis-get command + - Running the !gti-privatescanning-analysis-get command (pending) Then: - Validate the command results are valid @@ -1071,26 +1137,90 @@ def test_gti_private_analysis_get(mocker, requests_mock): mock_response = { 'data': { 'attributes': { - 'stats': { - 'threat_severity_level': '', - 'popular_threat_category': '', - 'threat_verdict': '', - }, 'status': 'pending', } } } expected_response = mock_response.copy() expected_response['id'] = 'random_id' + + mocker.patch.object(demisto, 'args', return_value={'id': 'random_id'}) + requests_mock.get('https://www.virustotal.com/api/v3/private/analyses/random_id', + json=mock_response) + + results = private_get_analysis_command(client=client, args=demisto.args()) + + assert results.execution_metrics is None + assert results.outputs == expected_response + + +def test_completed_gti_private_analysis_get(mocker, requests_mock): + """ + Given: + - A valid analysis ID + + When: + - Running the !gti-privatescanning-analysis-get command (completed) + + Then: + - Validate the command results are valid + """ + from GoogleThreatIntelligence import private_get_analysis_command, Client + import CommonServerPython + + mocker.patch.object(demisto, 'params', return_value=DEFAULT_PARAMS) + mocker.patch.object(CommonServerPython, 'is_demisto_version_ge', return_value=True) + params = demisto.params() + client = Client(params=params) + + mock_analysis_response = { + 'data': { + 'attributes': { + 'status': 'completed', + } + } + } + mock_item_response = { + 'data': { + 'attributes': { + # File attributes + 'sha256': 'random_sha256', + 'threat_severity': { + 'threat_severity_level': 'SEVERITY_LOW', + 'threat_severity_data': { + 'popular_threat_category': 'random_category', + }, + }, + 'threat_verdict': 'VERDICT_UNDETECTED', + # URL attributes + 'url': 'random_url', + 'title': 'random_title', + 'last_http_response_content_sha256': 'random_content_sha256', + 'last_analysis_stats': { + 'malicious': 1, + 'undetected': 4, + } + } + } + } + expected_response = mock_analysis_response.copy() + expected_response['id'] = 'random_id' expected_response['data']['attributes'].update({ - 'threat_severity_level': '', - 'popular_threat_category': '', - 'threat_verdict': '', + 'sha256': 'random_sha256', + 'threat_severity_level': 'LOW', + 'popular_threat_category': 'random_category', + 'threat_verdict': 'UNDETECTED', + 'url': 'random_url', + 'title': 'random_title', + 'last_http_response_content_sha256': 'random_content_sha256', + 'positives': '1/5', }) mocker.patch.object(demisto, 'args', return_value={'id': 'random_id'}) requests_mock.get('https://www.virustotal.com/api/v3/private/analyses/random_id', - json=mock_response) + json=mock_analysis_response) + requests_mock.get('https://www.virustotal.com/api/v3/private/analyses/random_id/item', + json=mock_item_response) results = private_get_analysis_command(client=client, args=demisto.args()) @@ -1138,6 +1268,46 @@ def test_url_scan_command(mocker, requests_mock): } +def test_private_url_scan_command(mocker, requests_mock): + """ + Given: + - A valid URL + + When: + - Running the !gti-privatescanning-url-scan command + + Then: + - Validate the command results are valid + """ + from GoogleThreatIntelligence import private_scan_url_command, Client + import CommonServerPython + + mocker.patch.object(demisto, 'params', return_value=DEFAULT_PARAMS) + mocker.patch.object(CommonServerPython, 'is_demisto_version_ge', return_value=True) + params = demisto.params() + client = Client(params=params) + + url = 'https://www.example.com' + mock_response = { + 'data': { + 'id': 'random_id', + 'url': url, + } + } + + mocker.patch.object(demisto, 'args', return_value={'url': url}) + requests_mock.post('https://www.virustotal.com/api/v3/private/urls', + json=mock_response) + + results = private_scan_url_command(client=client, args=demisto.args()) + + assert results.execution_metrics is None + assert results.outputs == { + 'GoogleThreatIntelligence.Submission(val.id && val.id === obj.id)': mock_response['data'], + 'vtScanID': 'random_id', + } + + def test_file_sigma_analysis_command(mocker, requests_mock): """ Given: diff --git a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/README.md b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/README.md index d3d83b10eb7b..d9ab1d53ba38 100644 --- a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/README.md +++ b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/README.md @@ -2466,7 +2466,7 @@ Submits a file for private scanning. Use the gti-privatescanning-analysis-get co ### gti-privatescanning-analysis-get *** -Get analysis of a private file submitted to GoogleThreatIntelligence. +Get analysis of a private file or URL submitted to GoogleThreatIntelligence. #### Base Command @@ -2476,67 +2476,32 @@ Get analysis of a private file submitted to GoogleThreatIntelligence. | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| id | ID of the analysis. | Required | +| id | ID of the analysis. | Required | #### Context Output | **Path** | **Type** | **Description** | | --- | --- | --- | -| GoogleThreatIntelligence.Analysis.data.attributes.date | Number | Date of the analysis in epoch format. | -| GoogleThreatIntelligence.Analysis.data.attributes.status | String | Status of the analysis. | -| GoogleThreatIntelligence.Analysis.data.attributes.threat_severity_level | String | Threat severity level of the private file. | -| GoogleThreatIntelligence.Analysis.data.attributes.popular_threat_category | String | Popular threat category of the private file. | -| GoogleThreatIntelligence.Analysis.data.attributes.threat_verdict | String | Threat verdict of the private file. | -| GoogleThreatIntelligence.Analysis.data.id | String | ID of the analysis. | -| GoogleThreatIntelligence.Analysis.data.type | String | Type of object (analysis). | -| GoogleThreatIntelligence.Analysis.meta.file_info.sha256 | String | SHA-256 hash of the file (if it is a file). | -| GoogleThreatIntelligence.Analysis.meta.file_info.sha1 | String | SHA-1 hash of the file (if it is a file). | -| GoogleThreatIntelligence.Analysis.meta.file_info.md5 | String | MD5 hash of the file (if it is a file). | -| GoogleThreatIntelligence.Analysis.meta.file_info.size | Number | Size of the file (if it is a file). | -| GoogleThreatIntelligence.Analysis.id | String | The analysis ID. | - -#### Command Example - -```!gti-privatescanning-analysis-get id=example-analysis-id``` - -#### Context Example - -```json -{ - "GoogleThreatIntelligence": { - "Analysis": { - "id": "example-analysis-id", - "meta": { - "file_info": { - "sha256": "Example_sha256", - "sha1": "Example_sha1", - "md5": "Example_md5", - "size": 48 - } - }, - "data": { - "attributes": { - "date": 1681461324, - "status": "completed", - "threat_severity_level": "SEVERITY_HIGH", - "popular_threat_category": "trojan", - "threat_verdict": "VERDICT_MALICIOUS", - }, - "type": "private_analysis", - "id": "example-analysis-id" - } - } - } -} -``` - -#### Human Readable Output +| GoogleThreatIntelligence.Analysis.data.attributes.date | Number | Date of the analysis in epoch format. | +| GoogleThreatIntelligence.Analysis.data.attributes.status | String | Status of the analysis. | +| GoogleThreatIntelligence.Analysis.data.attributes.sha256 | String | SHA-256 hash of the private file. | +| GoogleThreatIntelligence.Analysis.data.attributes.threat_severity_level | String | Threat severity level of the private file \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.popular_threat_category | String | Popular threat category of the private file \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.threat_verdict | String | Threat verdict of the private file \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.url | String | URL submitted. | +| GoogleThreatIntelligence.Analysis.data.attributes.title | String | Title of the private URL \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.last_http_response_content_sha256 | String | Last HTTP response content SHA-256 hash of the private URL \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.positives | String | Ratio of malicious detections to the total number of engines that scanned the private URL \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.id | String | ID of the analysis. | +| GoogleThreatIntelligence.Analysis.data.type | String | Type of object \(private_analysis\). | +| GoogleThreatIntelligence.Analysis.meta.file_info.sha256 | String | SHA-256 hash of the file. | +| GoogleThreatIntelligence.Analysis.meta.file_info.sha1 | String | SHA-1 hash of the file. | +| GoogleThreatIntelligence.Analysis.meta.file_info.md5 | String | MD5 hash of the file. | +| GoogleThreatIntelligence.Analysis.meta.file_info.size | Number | Size of the file. | +| GoogleThreatIntelligence.Analysis.meta.url_info.id | String | ID of the URL. | +| GoogleThreatIntelligence.Analysis.meta.url_info.url | String | URL submitted. | +| GoogleThreatIntelligence.Analysis.id | String | The analysis ID. | ->### Analysis results -> ->|Id|Threat Severity Level|Popular Threat Category|Threat Verdict|Status| ->|---|---|---|---|---|---|---| ->| example-analysis-id | HIGH | trojan | MALICIOUS | completed | ### gti-curated-threat-actors-get *** @@ -2564,6 +2529,7 @@ Retrieves GTI curated threat actors for a given resource. | GoogleThreatIntelligence.Collection.collections.attributes.last_modification_date | String | Last modification date of the curated threat actors. | | GoogleThreatIntelligence.Collection.collections.attributes.targeted_regions | list | Targeted regions of the curated threat actors. | | GoogleThreatIntelligence.Collection.collections.attributes.targeted_industries | list | Targeted industries of the curated threat actors. | + ### gti-curated-malware-families-get *** @@ -2591,6 +2557,7 @@ Retrieves GTI curated malware families for a given resource. | GoogleThreatIntelligence.Collection.collections.attributes.last_modification_date | String | Last modification date of the curated malware families. | | GoogleThreatIntelligence.Collection.collections.attributes.targeted_regions | list | Targeted regions of the curated malware families. | | GoogleThreatIntelligence.Collection.collections.attributes.targeted_industries | list | Targeted industries of the curated malware families. | + ### gti-curated-campaigns-get *** @@ -2618,6 +2585,7 @@ Retrieves GTI curated campaigns for a given resource. | GoogleThreatIntelligence.Collection.collections.attributes.last_modification_date | String | Last modification date of the curated campaign. | | GoogleThreatIntelligence.Collection.collections.attributes.targeted_regions | list | Targeted regions of the curated campaign. | | GoogleThreatIntelligence.Collection.collections.attributes.targeted_industries | list | Targeted industries of the curated campaign. | + ### gti-url-scan-and-analysis-get *** @@ -2699,6 +2667,7 @@ Scan and get the analysis of a URL submitted to GoogleThreatIntelligence. | GoogleThreatIntelligence.Analysis.meta.url_info.id | String | ID of the URL. | | GoogleThreatIntelligence.Analysis.meta.url_info.url | String | The URL. | | GoogleThreatIntelligence.Analysis.id | String | The analysis ID. | + ### gti-file-scan-and-analysis-get *** @@ -2818,6 +2787,43 @@ Scan and get the analysis of a file submitted to GoogleThreatIntelligence. | GoogleThreatIntelligence.Analysis.meta.file_info.name | unknown | Name of the file. | | GoogleThreatIntelligence.Analysis.meta.file_info.size | Number | Size of the file. | | GoogleThreatIntelligence.Analysis.id | String | The analysis ID. | + +### gti-private-file-scan-and-analysis-get + +*** +Scan and get the analysis of a private file submitted to GoogleThreatIntelligence. + +#### Base Command + +`gti-private-file-scan-and-analysis-get` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| entryID | The file entry ID to submit. | Required | +| id | This is an internal argument used for the polling process, not to be used by the user. | Optional | +| extended_data | Whether to return extended data. Possible values are: true, false. | Optional | +| interval_in_seconds | Interval in seconds between each poll. Default is 60. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| GoogleThreatIntelligence.Analysis.data.attributes.date | Number | Date of the analysis in epoch format. | +| GoogleThreatIntelligence.Analysis.data.attributes.status | String | Status of the analysis. | +| GoogleThreatIntelligence.Analysis.data.attributes.sha256 | String | SHA-256 hash of the private file. | +| GoogleThreatIntelligence.Analysis.data.attributes.threat_severity_level | String | Threat severity level of the private file \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.popular_threat_category | String | Popular threat category of the private file \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.threat_verdict | String | Threat verdict of the private file \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.id | String | ID of the analysis. | +| GoogleThreatIntelligence.Analysis.data.type | String | Type of object \(private_analysis\). | +| GoogleThreatIntelligence.Analysis.meta.file_info.sha256 | String | SHA-256 hash of the file. | +| GoogleThreatIntelligence.Analysis.meta.file_info.sha1 | String | SHA-1 hash of the file. | +| GoogleThreatIntelligence.Analysis.meta.file_info.md5 | String | MD5 hash of the file. | +| GoogleThreatIntelligence.Analysis.meta.file_info.size | Number | Size of the file. | +| GoogleThreatIntelligence.Analysis.id | String | The analysis ID. | + ### gti-private-file-scan-and-analysis-get *** @@ -2852,6 +2858,7 @@ Scan and get the analysis of a private file submitted to GoogleThreatIntelligenc | GoogleThreatIntelligence.Analysis.meta.file_info.md5 | String | MD5 hash of the file. | | GoogleThreatIntelligence.Analysis.meta.file_info.size | Number | Size of the file. | | GoogleThreatIntelligence.Analysis.id | String | The analysis ID. | + ### gti-assessment-get *** @@ -2875,4 +2882,100 @@ Retrieves GTI assessment for a given resource. | GoogleThreatIntelligence.Assessment.id | String | ID that contains the assessment \(the given hash, domain, url, or ip\). | | GoogleThreatIntelligence.Assessment.attributes.gti_assessment.threat_score.value | Number | The threat score of the assessment. | | GoogleThreatIntelligence.Assessment.attributes.gti_assessment.severity.value | String | The severity of the assessment. | -| GoogleThreatIntelligence.Assessment.attributes.gti_assessment.verdict.value | String | The verdict of the assessment. | \ No newline at end of file +| GoogleThreatIntelligence.Assessment.attributes.gti_assessment.verdict.value | String | The verdict of the assessment. | + +### gti-private-url-scan-and-analysis-get + +*** +Scan and get the analysis of a private URL submitted to GoogleThreatIntelligence. + +#### Base Command + +`gti-private-url-scan-and-analysis-get` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| url | The URL to scan. | Required | +| id | This is an internal argument used for the polling process, not to be used by the user. | Optional | +| extended_data | Whether to return extended data. Possible values are: true, false. | Optional | +| interval_in_seconds | Interval in seconds between each poll. Default is 60. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| GoogleThreatIntelligence.Analysis.data.attributes.date | Number | Date of the analysis in epoch format. | +| GoogleThreatIntelligence.Analysis.data.attributes.status | String | Status of the analysis. | +| GoogleThreatIntelligence.Analysis.data.attributes.url | String | URL submitted. | +| GoogleThreatIntelligence.Analysis.data.attributes.title | String | Title of the private URL \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.last_http_response_content_sha256 | String | Last HTTP response content SHA-256 hash of the private URL \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.attributes.positives | String | Ratio of malicious detections to the total number of engines that scanned the private URL \(if analysis is completed\). | +| GoogleThreatIntelligence.Analysis.data.id | String | ID of the analysis. | +| GoogleThreatIntelligence.Analysis.data.type | String | Type of object \(private_analysis\). | +| GoogleThreatIntelligence.Analysis.meta.url_info.id | String | ID of the URL. | +| GoogleThreatIntelligence.Analysis.meta.url_info.url | String | URL submitted. | +| GoogleThreatIntelligence.Analysis.id | String | The analysis ID. | + +### gti-privatescanning-url-scan + +*** + +#### Base Command + +`gti-privatescanning-url-scan` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| url | The private URL to scan. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| GoogleThreatIntelligence.Submission.Type | String | The type of the submission \(private_analysis\). | +| GoogleThreatIntelligence.Submission.id | String | The ID of the submission. | + +### gti-privatescanning-url + +*** +Checks the reputation of a private URL. + +#### Base Command + +`gti-privatescanning-url` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| url | Private URL to check. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| GoogleThreatIntelligence.URL.attributes.favicon.raw_md5 | String | The MD5 hash of the URL. | +| GoogleThreatIntelligence.URL.attributes.favicon.dhash | String | Difference hash. | +| GoogleThreatIntelligence.URL.attributes.last_http_response_content_length | Number | The last HTTPS response length. | +| GoogleThreatIntelligence.URL.attributes.last_http_response_headers.date | Date | The last response header date. | +| GoogleThreatIntelligence.URL.attributes.last_http_response_headers.x-sinkhole | String | DNS sinkhole from last response. | +| GoogleThreatIntelligence.URL.attributes.last_http_response_headers.content-length | String | The content length of the last response. | +| GoogleThreatIntelligence.URL.attributes.last_http_response_headers.content-type | String | The content type of the last response. | +| GoogleThreatIntelligence.URL.attributes.last_http_response_content_sha256 | String | The SHA-256 hash of the content of the last response. | +| GoogleThreatIntelligence.URL.attributes.last_http_response_code | Number | Last response status code. | +| GoogleThreatIntelligence.URL.attributes.last_final_url | String | Last final URL. | +| GoogleThreatIntelligence.URL.attributes.url | String | The URL itself. | +| GoogleThreatIntelligence.URL.attributes.title | String | Title of the page. | +| GoogleThreatIntelligence.URL.attributes.last_analysis_stats.harmless | Number | The number of engines that found the domain to be harmless. | +| GoogleThreatIntelligence.URL.attributes.last_analysis_stats.malicious | Number | The number of engines that found the indicator to be malicious. | +| GoogleThreatIntelligence.URL.attributes.last_analysis_stats.suspicious | Number | The number of engines that found the indicator to be suspicious. | +| GoogleThreatIntelligence.URL.attributes.last_analysis_stats.undetected | Number | The number of engines that could not detect the indicator. | +| GoogleThreatIntelligence.URL.attributes.last_analysis_stats.timeout | Number | The number of engines that timed out for the indicator. | +| GoogleThreatIntelligence.URL.attributes.outgoing_links | String | Outgoing links of the URL page. | +| GoogleThreatIntelligence.URL.type | String | Type of the indicator \(private_url\). | +| GoogleThreatIntelligence.URL.id | String | ID of the indicator. | +| GoogleThreatIntelligence.URL.links.self | String | Link to the response. | diff --git a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/test_data/private_url.json b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/test_data/private_url.json new file mode 100644 index 000000000000..1752d398b6ee --- /dev/null +++ b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/test_data/private_url.json @@ -0,0 +1,210 @@ +{ + "data": { + "id": "0e682ecaa41a32ae3715fce74bac96d6d763bad6d149677e3a75fcf20c764113", + "type": "private_url", + "links": { + "self": "https://www.virustotal.com/api/v3/private/urls/0e682ecaa41a32ae3715fce74bac96d6d763bad6d149677e3a75fcf20c764113" + }, + "attributes": { + "favicon": { + "raw_md5": "cbf0440f4af60a3f3ea4b875128c4b0c", + "dhash": "3333330b2b333333" + }, + "last_http_response_headers": { + "cache-control": "s-maxage=60", + "content-encoding": "gzip", + "date": "Wed, 22 Jan 2025 15:41:27 GMT", + "referrer-policy": "unsafe-url", + "x-cache-hits": "2, 1", + "age": "45", + "x-served-by": "cache-ams21070-AMS, cache-chi-klot8100105-CHI", + "content-type": "text/html; charset=UTF-8", + "content-length": "54280", + "x-backend": "CONTENT", + "x-timer": "S1737560487.186373,VS0,VE2", + "accept-ranges": "bytes", + "x-cache": "HIT, HIT", + "vary": "Accept-Encoding, User-Agent" + }, + "last_analysis_results": { + "Google Safebrowsing": { + "method": "blacklist", + "engine_name": "Google Safebrowsing", + "category": "harmless", + "result": "clean" + } + }, + "trackers": { + "Amazon Associates": [ + { + "url": "//c.amazon-adsystem.com/aax2/apstag.js", + "id": "", + "timestamp": 1737560484 + } + ], + "Criteo": [ + { + "url": "https://static.criteo.net/js/ld/publishertag.prebid.139.js", + "id": "", + "timestamp": 1737560484 + } + ], + "Facebook Connect": [ + { + "url": "", + "id": "", + "timestamp": 1737560484 + } + ], + "Google Analytics": [ + { + "url": "https://www.google-analytics.com/analytics.js", + "id": "", + "timestamp": 1737560484 + } + ], + "Google Publisher Tags": [ + { + "url": "https://securepubads.g.doubleclick.net/tag/js/gpt.js", + "id": "", + "timestamp": 1737560484 + } + ], + "Google Tag Manager": [ + { + "url": "https://www.googletagmanager.com/gtag/js?id=G-V1WMLM3DL4&l=dataLayerUE&cx=c>m=457e51l0za200", + "id": "G-V1WMLM3DL4", + "timestamp": 1737560484 + }, + { + "url": "https://www.googletagmanager.com/gtag/js?id=UA-181957286-2&l=dataLayerUE", + "id": "UA-181957286-2", + "timestamp": 1737560484 + } + ], + "Taboola": [ + { + "url": "https://cdn.taboola.com/scripts/eid.es5.js", + "id": "", + "timestamp": 1737560484 + }, + { + "url": "//cdn.taboola.com/libtrc/unip/1171464/tfa.js", + "id": "1171464", + "timestamp": 1737560484 + } + ] + }, + "last_http_response_content_length": 243836, + "last_http_response_code": 200, + "last_http_response_content_sha256": "a71efb8e3bcd99e1c1360a43d29ecd2a8e3d56ee74b2cf254ec2d6cf80528022", + "last_final_url": "https://www.marca.com/futbol/barcelona/2025/01/22/porteria-barca-pie-guerra.html", + "last_analysis_stats": { + "malicious": 0, + "suspicious": 0, + "undetected": 0, + "harmless": 1, + "timeout": 0 + }, + "redirection_chain": [ + "https://www.marca.com/futbol/barcelona/2025/01/22/porteria-barca-pie-guerra.html" + ], + "gti_score_age_off_rescore_count": 0, + "expiration": 1737646884, + "tld": "com", + "url": "https://www.marca.com/futbol/barcelona/2025/01/22/porteria-barca-pie-guerra.html", + "outgoing_links": [ + "http://cookies.unidadeditorial.es/", + "https://us.marca.com/?intcmp=BOTONPORTADA&s_kw=portada" + ], + "html_meta": { + "viewport": [ + "width=device-width" + ], + "title": [ + "La portería del Barça, en pie de 'guerra' | Marca" + ], + "description": [ + "Flick no da por sentada ni la titularidad de Szczesny ni la de Iñaki Peña" + ], + "robots": [ + "index, follow, max-image-preview:large" + ], + "date": [ + "2025-01-22T15:19:11Z" + ], + "article:modified_time": [ + "2025-01-22T15:19:11Z" + ], + "article:published_time": [ + "2025-01-22T15:19:11Z" + ], + "DC.date.issued": [ + "2025-01-22T15:19:11Z" + ], + "organization": [ + "Unidad Editorial Información Deportiva S.L." + ], + "article:section": [ + "Barcelona" + ], + "og:url": [ + "https://www.marca.com/futbol/barcelona/2025/01/22/porteria-barca-pie-guerra.html" + ], + "og:type": [ + "article" + ], + "og:site_name": [ + "MARCA" + ], + "og:title": [ + "La portería del Barça, en pie de 'guerra'" + ], + "og:description": [ + "Flick no da por sentada ni la titularidad de Szczesny ni la de Iñaki Peña" + ], + "og:updated_time": [ + "2025-01-22T15:19:11Z" + ], + "fb:app_id": [ + "1204085963868279" + ], + "twitter:card": [ + "summary_large_image" + ], + "twitter:site": [ + "@marca" + ], + "twitter:creator": [ + "@marca" + ], + "twitter:title": [ + "La portería del Barça, en pie de 'guerra'" + ], + "twitter:description": [ + "Flick no da por sentada ni la titularidad de Szczesny ni la de Iñaki Peña" + ], + "og:image": [ + "https://e00-xlk-ue-marca.uecdn.es/files/phantom_desktop_1200w/uploads/2025/01/22/6790e401a13df.jpeg" + ], + "twitter:image": [ + "https://e00-xlk-ue-marca.uecdn.es/files/phantom_desktop_1200w/uploads/2025/01/22/6790e401a13df.jpeg" + ], + "mrf:sections": [ + "futbol" + ], + "mrf:tags": [ + "sub-section:barcelona", + "contentType:article" + ] + }, + "tags": [ + "iframes", + "trackers", + "third-party-cookies", + "external-resources" + ], + "title": "La portería del Barça, en pie de 'guerra' | Marca" + } + } +} \ No newline at end of file diff --git a/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/test_data/private_url_results.json b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/test_data/private_url_results.json new file mode 100644 index 000000000000..39a8b1dae72d --- /dev/null +++ b/Packs/GoogleThreatIntelligence/Integrations/GoogleThreatIntelligence/test_data/private_url_results.json @@ -0,0 +1,208 @@ +{ + "id": "0e682ecaa41a32ae3715fce74bac96d6d763bad6d149677e3a75fcf20c764113", + "type": "private_url", + "links": { + "self": "https://www.virustotal.com/api/v3/private/urls/0e682ecaa41a32ae3715fce74bac96d6d763bad6d149677e3a75fcf20c764113" + }, + "attributes": { + "favicon": { + "raw_md5": "cbf0440f4af60a3f3ea4b875128c4b0c", + "dhash": "3333330b2b333333" + }, + "last_http_response_headers": { + "cache-control": "s-maxage=60", + "content-encoding": "gzip", + "date": "Wed, 22 Jan 2025 15:41:27 GMT", + "referrer-policy": "unsafe-url", + "x-cache-hits": "2, 1", + "age": "45", + "x-served-by": "cache-ams21070-AMS, cache-chi-klot8100105-CHI", + "content-type": "text/html; charset=UTF-8", + "content-length": "54280", + "x-backend": "CONTENT", + "x-timer": "S1737560487.186373,VS0,VE2", + "accept-ranges": "bytes", + "x-cache": "HIT, HIT", + "vary": "Accept-Encoding, User-Agent" + }, + "last_analysis_results": { + "Google Safebrowsing": { + "method": "blacklist", + "engine_name": "Google Safebrowsing", + "category": "harmless", + "result": "clean" + } + }, + "trackers": { + "Amazon Associates": [ + { + "url": "//c.amazon-adsystem.com/aax2/apstag.js", + "id": "", + "timestamp": 1737560484 + } + ], + "Criteo": [ + { + "url": "https://static.criteo.net/js/ld/publishertag.prebid.139.js", + "id": "", + "timestamp": 1737560484 + } + ], + "Facebook Connect": [ + { + "url": "", + "id": "", + "timestamp": 1737560484 + } + ], + "Google Analytics": [ + { + "url": "https://www.google-analytics.com/analytics.js", + "id": "", + "timestamp": 1737560484 + } + ], + "Google Publisher Tags": [ + { + "url": "https://securepubads.g.doubleclick.net/tag/js/gpt.js", + "id": "", + "timestamp": 1737560484 + } + ], + "Google Tag Manager": [ + { + "url": "https://www.googletagmanager.com/gtag/js?id=G-V1WMLM3DL4&l=dataLayerUE&cx=c>m=457e51l0za200", + "id": "G-V1WMLM3DL4", + "timestamp": 1737560484 + }, + { + "url": "https://www.googletagmanager.com/gtag/js?id=UA-181957286-2&l=dataLayerUE", + "id": "UA-181957286-2", + "timestamp": 1737560484 + } + ], + "Taboola": [ + { + "url": "https://cdn.taboola.com/scripts/eid.es5.js", + "id": "", + "timestamp": 1737560484 + }, + { + "url": "//cdn.taboola.com/libtrc/unip/1171464/tfa.js", + "id": "1171464", + "timestamp": 1737560484 + } + ] + }, + "last_http_response_content_length": 243836, + "last_http_response_code": 200, + "last_http_response_content_sha256": "a71efb8e3bcd99e1c1360a43d29ecd2a8e3d56ee74b2cf254ec2d6cf80528022", + "last_final_url": "https://www.marca.com/futbol/barcelona/2025/01/22/porteria-barca-pie-guerra.html", + "last_analysis_stats": { + "malicious": 0, + "suspicious": 0, + "undetected": 0, + "harmless": 1, + "timeout": 0 + }, + "redirection_chain": [ + "https://www.marca.com/futbol/barcelona/2025/01/22/porteria-barca-pie-guerra.html" + ], + "gti_score_age_off_rescore_count": 0, + "expiration": 1737646884, + "tld": "com", + "url": "https://www.marca.com/futbol/barcelona/2025/01/22/porteria-barca-pie-guerra.html", + "outgoing_links": [ + "http://cookies.unidadeditorial.es/", + "https://us.marca.com/?intcmp=BOTONPORTADA&s_kw=portada" + ], + "html_meta": { + "viewport": [ + "width=device-width" + ], + "title": [ + "La portería del Barça, en pie de 'guerra' | Marca" + ], + "description": [ + "Flick no da por sentada ni la titularidad de Szczesny ni la de Iñaki Peña" + ], + "robots": [ + "index, follow, max-image-preview:large" + ], + "date": [ + "2025-01-22T15:19:11Z" + ], + "article:modified_time": [ + "2025-01-22T15:19:11Z" + ], + "article:published_time": [ + "2025-01-22T15:19:11Z" + ], + "DC.date.issued": [ + "2025-01-22T15:19:11Z" + ], + "organization": [ + "Unidad Editorial Información Deportiva S.L." + ], + "article:section": [ + "Barcelona" + ], + "og:url": [ + "https://www.marca.com/futbol/barcelona/2025/01/22/porteria-barca-pie-guerra.html" + ], + "og:type": [ + "article" + ], + "og:site_name": [ + "MARCA" + ], + "og:title": [ + "La portería del Barça, en pie de 'guerra'" + ], + "og:description": [ + "Flick no da por sentada ni la titularidad de Szczesny ni la de Iñaki Peña" + ], + "og:updated_time": [ + "2025-01-22T15:19:11Z" + ], + "fb:app_id": [ + "1204085963868279" + ], + "twitter:card": [ + "summary_large_image" + ], + "twitter:site": [ + "@marca" + ], + "twitter:creator": [ + "@marca" + ], + "twitter:title": [ + "La portería del Barça, en pie de 'guerra'" + ], + "twitter:description": [ + "Flick no da por sentada ni la titularidad de Szczesny ni la de Iñaki Peña" + ], + "og:image": [ + "https://e00-xlk-ue-marca.uecdn.es/files/phantom_desktop_1200w/uploads/2025/01/22/6790e401a13df.jpeg" + ], + "twitter:image": [ + "https://e00-xlk-ue-marca.uecdn.es/files/phantom_desktop_1200w/uploads/2025/01/22/6790e401a13df.jpeg" + ], + "mrf:sections": [ + "futbol" + ], + "mrf:tags": [ + "sub-section:barcelona", + "contentType:article" + ] + }, + "tags": [ + "iframes", + "trackers", + "third-party-cookies", + "external-resources" + ], + "title": "La portería del Barça, en pie de 'guerra' | Marca" + } +} \ No newline at end of file diff --git a/Packs/GoogleThreatIntelligence/ReleaseNotes/1_2_0.md b/Packs/GoogleThreatIntelligence/ReleaseNotes/1_2_0.md new file mode 100644 index 000000000000..0a5d7064b90e --- /dev/null +++ b/Packs/GoogleThreatIntelligence/ReleaseNotes/1_2_0.md @@ -0,0 +1,10 @@ + +#### Integrations + +##### Google Threat Intelligence + +- Added the **gti-privatescanning-url** command to check the reputation of a private URL. +- Added the **gti-privatescanning-url-scan** command to submit an URL for private scanning. +- Added the **gti-private-url-scan-and-analysis-get** polling command to scan and get the analysis of a private URL. +- Updated the **gti-privatescanning-analysis-get** command to include URL analyses. +- Updated the Docker image to: *demisto/python3:3.12.8.1983910*. diff --git a/Packs/GoogleThreatIntelligence/pack_metadata.json b/Packs/GoogleThreatIntelligence/pack_metadata.json index a1d80379045b..7faea84e7d53 100644 --- a/Packs/GoogleThreatIntelligence/pack_metadata.json +++ b/Packs/GoogleThreatIntelligence/pack_metadata.json @@ -2,7 +2,7 @@ "name": "GoogleThreatIntelligence", "description": "Analyze suspicious hashes, URLs, domains and IP addresses", "support": "partner", - "currentVersion": "1.1.1", + "currentVersion": "1.2.0", "author": "Google Threat Intelligence", "url": "https://www.virustotal.com", "email": "contact@virustotal.com",