diff --git a/CHANGELOG.md b/CHANGELOG.md index 85679730f..bfd66fa07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [3.4.2](https://github.com/TheHive-Project/Cortex-Analyzers/tree/3.4.2) (2024-12-24) + +[Full Changelog](https://github.com/TheHive-Project/Cortex-Analyzers/compare/3.4.1...3.4.2) + +**Closed issues:** + +- \[FR\] CrowdStrike Falcon - Implement TheHive custom user-agent [\#1306](https://github.com/TheHive-Project/Cortex-Analyzers/issues/1306) +- \[FR\] Analyzer for observable validation [\#1305](https://github.com/TheHive-Project/Cortex-Analyzers/issues/1305) +- \[FR\] New Analyzer: Axur Ioc's \(WIP\) [\#1190](https://github.com/TheHive-Project/Cortex-Analyzers/issues/1190) + +## [3.4.1](https://github.com/TheHive-Project/Cortex-Analyzers/tree/3.4.1) (2024-12-17) + +[Full Changelog](https://github.com/TheHive-Project/Cortex-Analyzers/compare/3.4.0...3.4.1) + +**Merged pull requests:** + +- Capa Analyzer - auto-download latest capa binary [\#1301](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1301) ([nusantara-self](https://github.com/nusantara-self)) +- Domaintools dependency issues [\#1300](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1300) ([nusantara-self](https://github.com/nusantara-self)) +- Fix anyrun\_analyzer.py for submit file [\#1299](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1299) ([winl0gon](https://github.com/winl0gon)) +- add proxies to phishtank\_checkurl.py [\#1298](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1298) ([Labuiga](https://github.com/Labuiga)) + ## [3.4.0](https://github.com/TheHive-Project/Cortex-Analyzers/tree/3.4.0) (2024-12-09) [Full Changelog](https://github.com/TheHive-Project/Cortex-Analyzers/compare/3.3.8...3.4.0) diff --git a/analyzers/Axur/README.md b/analyzers/Axur/README.md new file mode 100644 index 000000000..70a01833d --- /dev/null +++ b/analyzers/Axur/README.md @@ -0,0 +1,104 @@ +### Axur Ioc's analyzer + +The Axur IOC Analyzer is a tool for identifying and value potential threats in your data. It uses Axur's services and databases to perform analysis on a variety of data types. + +The Analyzer provides an efficient solution to evaluate potential threats by examining various data types including: + +* _domain_ +* _url_ +* _ip_ +* _fqdn_ +* _hash_ + +With the Axur IOC Analyzer, Axur clients have an easy way to make their data environment safer and more secure. + +#### Requirements +You need a valid Axur API key to use the analyzer. Available exclusively to our Axur clients. + +- Provide your API key as values for the `api_key` header. + +### Return example + +``` +{ + "success": true, + "summary": { + "taxonomies": [ + { + "level": "suspicious", + "namespace": "Axur", + "predicate": "IOC_FEED", + "value": 2 + }, + { + "level": "suspicious", + "namespace": "Axur", + "predicate": "EXPLORE", + "value": 1 + }, + { + "level": "suspicious", + "namespace": "Axur", + "predicate": "MALICIOUS_URL", + "value": 1 + } + ] + }, + "artifacts": [], + "operations": [], + "full": { + "type": "URL", + "value": "https://sso.ecometrica.com/accounts/login", + "results": [ + { + "source": "IOC_FEED", + "score": 2, + "hits": 2, + "context": [ + { + "tags": [ + "phishing" + ], + "detection": 1683945464000, + "risk": "UNDEFINED", + "platform": "AXUR" + }, + { + "tags": [], + "detection": 1642009957000, + "risk": "MEDIUM", + "platform": "AXUR" + } + ] + }, + { + "source": "EXPLORE", + "score": 2, + "hits": 1, + "context": [ + { + "content": "texto", + "detection": 1687187006704, + "platform": "AXUR" + } + ] + }, + { + "source": "MALICIOUS_URL", + "score": 2, + "hits": 1, + "context": [ + { + "riskLevel": 0.49, + "collectorName": "urlscan", + "detection": 1687187006704, + "ticketStatus": "open", + "platform": "AXUR" + } + ] + } + ], + "searchDate": 1687292305787 + } +} +``` diff --git a/analyzers/Axur/axur_analyzer.json b/analyzers/Axur/axur_analyzer.json new file mode 100644 index 000000000..b8da62c08 --- /dev/null +++ b/analyzers/Axur/axur_analyzer.json @@ -0,0 +1,24 @@ +{ + "name": "Axur", + "author": "Axur", + "version": "1.0", + "license": "AGPL-V3", + "url": "https://github.com/TheHive-Project/Cortex-Analyzers", + "description": "Search IPs, domains, hashes or URLs on axur.com", + "dataTypeList": ["domain", "fqdn", "ip", "url", "hash"], + "command": "Axur/axur_analyzer.py", + "baseConfig": "Axur", + "configurationItems": [ + { + "name": "api_key", + "description": "Define the API key", + "type": "string", + "multi": false, + "required": true + } + ], + "registration_required": true, + "subscription_required": true, + "free_subscription": false, + "service_homepage": "https://www.axur.com" +} diff --git a/analyzers/Axur/axur_analyzer.py b/analyzers/Axur/axur_analyzer.py new file mode 100644 index 000000000..f1f4160f5 --- /dev/null +++ b/analyzers/Axur/axur_analyzer.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# encoding: utf-8 + +from cortexutils.analyzer import Analyzer +from urllib.parse import quote_plus +import requests + + +class AxurAnalyzer(Analyzer): + + def __init__(self): + Analyzer.__init__(self) + self.api_key = self.get_param( + 'config.api_key', None, 'Missing Axur API key' + ) + + def run(self): + if self.data_type not in ['domain', 'fqdn', 'ip', 'url', 'hash']: + self.error('Wrong data type') + + encoded_data = quote_plus(self.get_data()) + url = f'https://api.axur.com/gateway/1.0/api/ioc-search/search/{self.data_type}/{encoded_data}' + + try: + response = requests.get(url, headers={'Authorization': f'Bearer {self.api_key}'}) + response.raise_for_status() + self.report(response.json()) + except requests.HTTPError as http_err: + self.error('HTTP error occurred: {}'.format(http_err)) + except Exception as err: + self.error('Error occurred: {}'.format(err)) + + def summary(self, raw): + taxonomies = [] + levels = ['info', 'safe', 'suspicious', 'malicious'] + + for data in raw['results']: + level = levels[data.get('score', 0)] + taxonomies.append( + self.build_taxonomy(level, 'Axur', data['source'], data.get('hits', 0)) + ) + + return {'taxonomies': taxonomies} + + +if __name__ == '__main__': + AxurAnalyzer().run() diff --git a/analyzers/Axur/requirements.txt b/analyzers/Axur/requirements.txt new file mode 100644 index 000000000..6aabc3cfa --- /dev/null +++ b/analyzers/Axur/requirements.txt @@ -0,0 +1,2 @@ +cortexutils +requests diff --git a/analyzers/Cluster25/C25CortexAnalyzer_investigate.json b/analyzers/Cluster25/C25CortexAnalyzer_investigate.json new file mode 100644 index 000000000..98735d32f --- /dev/null +++ b/analyzers/Cluster25/C25CortexAnalyzer_investigate.json @@ -0,0 +1,53 @@ + { + "name": "C25CortexAnalyzer_Investigate", + "version": "1.0", + "author": "Cluster25", + "url": "https://github.com/TheHive-Project/Cortex-Analyzers", + "license": "AGPL-V3", + "description": "Use Cluster25's CTI API to investigate an observable.", + "dataTypeList": ["domain", "file", "hash", "ip", "mail", "url"], + "command": "c25-cortex-analyzer/c25_cortex_analyzer.py", + "baseConfig": "c25-cortex-analyzer", + "config": { + "check_tlp": false, + "check_pap": false, + "auto_extract_artifacts": true, + "service": "investigate" + }, + "configurationItems": [ + { + "name": "client_id", + "description": "Cluster25 CTI API credentials", + "type": "string", + "multi": false, + "required": true + }, + { + "name": "client_key", + "description": "Cluster25 CTI API credentials", + "type": "string", + "multi": false, + "required": true + }, + { + "name": "base_url", + "description": "Cluster25 CTI API base url", + "type": "string", + "multi": false, + "required": true + } + ], + "registration_required": true, + "subscription_required": true, + "free_subscription": false, + "service_homepage": "https://www.duskrise.com/the-c25-intelligence/", + "service_logo": {"path":"assets/cluster25_logo.png", "caption": "logo"}, + "screenshots": [ + {"path":"assets/short_report_sample.png", + "caption":"report sample" + }, + { + "path": "assets/long_report_sample.png", + "caption:":"report sample" + }] +} diff --git a/analyzers/Cluster25/Dockerfile b/analyzers/Cluster25/Dockerfile new file mode 100644 index 000000000..0da2f5866 --- /dev/null +++ b/analyzers/Cluster25/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.11 + +WORKDIR /worker +COPY . c25_analyzer + +RUN pip install --no-cache-dir -r c25_analyzer/requirements.txt +ENTRYPOINT c25_analyzer/c25_cortex_analyzer.py \ No newline at end of file diff --git a/analyzers/Cluster25/README.md b/analyzers/Cluster25/README.md new file mode 100644 index 000000000..41711ad69 --- /dev/null +++ b/analyzers/Cluster25/README.md @@ -0,0 +1,101 @@ +# Cluster25 Cortex Analyzer + +Allows to query Cluster25's CTI API investigation service. +Running the analyzer will return a short report with taxonomies, +as well as a long report and extracted artefacts. + +## Requirements: +* C25 API KEY +* C25 CLIENT ID +* C25 BASE URL + +Raw investigate result query example: +```json +{ + "indicator": "211.56.98.146", + "indicator_type": "ipv4", + "whitelisted": false, + "tags": [], + "score": 70, + "is_known": false, + "actors": [], + "related_indicators": { + "by_file": [], + "by_content": [] + }, + "related_contexts": [], + "created_dt": null, + "modified_dt": null, + "attacker_activities": [], + "targeted_sectors": [], + "targeted_countries": [], + "file_info": null, + "cve_info": null, + "asn_info": null, + "btcaddress_info": null, + "family_info": null, + "stats": { + "harmless": 61, + "malicious": 5, + "suspicious": 0, + "undetected": 23 + }, + "communicating_files": [], + "contacted_ips": [], + "contacted_domains": [], + "contacted_urls": [], + "dropped_files": [], + "passive_dns": { + "resolutions": [ + { + "record_name": "c3kr.simonxu.cc", + "record_value": "211.56.98.146", + "record_type": "A", + "first_seen": "2021-03-26T14:16:15", + "last_seen": "2021-03-26T14:16:55", + "country_name": "South Korea", + "$$hashKey": "object:64" + }, + { + "record_name": "counter.yadro.ru", + "record_value": "211.56.98.146", + "record_type": "A", + "first_seen": "2018-10-19T22:00:00", + "last_seen": "2018-10-19T22:00:00", + "country_name": "South Korea", + "$$hashKey": "object:65" + } + ] + }, + "whois": { + "ip": null, + "created_date": null, + "updated_date": "hostmaster@nic.or.kr", + "expires_date": null, + "registrant": { + "name": "IP Manager", + "organization": "Korea Telecom", + "street1": "Gyeonggi-do Bundang-gu, Seongnam-si Buljeong-ro 90", + "street2": null, + "city": null, + "state": null, + "country": null, + "country_code": null, + "postal_code": "13606", + "raw_text": null, + "unparsable": null + }, + "registrar_name": null, + "name_servers_hostnames": null, + "name_servers_ips": null, + "email_provider": null, + "email_registrant": null, + "status": null + }, + "guessed_types": [], + "intelligence": null, + "first_seen": null, + "last_seen": null, + "dns_resolutions": null +} +``` diff --git a/analyzers/Cluster25/assets/cluster25_logo.png b/analyzers/Cluster25/assets/cluster25_logo.png new file mode 100644 index 000000000..e201ca3d8 Binary files /dev/null and b/analyzers/Cluster25/assets/cluster25_logo.png differ diff --git a/analyzers/Cluster25/assets/long_report_sample.png b/analyzers/Cluster25/assets/long_report_sample.png new file mode 100644 index 000000000..ca2783f58 Binary files /dev/null and b/analyzers/Cluster25/assets/long_report_sample.png differ diff --git a/analyzers/Cluster25/assets/short_report_sample.png b/analyzers/Cluster25/assets/short_report_sample.png new file mode 100644 index 000000000..564ce7a56 Binary files /dev/null and b/analyzers/Cluster25/assets/short_report_sample.png differ diff --git a/analyzers/Cluster25/c25_cortex_analyzer.py b/analyzers/Cluster25/c25_cortex_analyzer.py new file mode 100644 index 000000000..237cf0528 --- /dev/null +++ b/analyzers/Cluster25/c25_cortex_analyzer.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +from typing import Optional, List + +import requests +from cortexutils.analyzer import Analyzer + + +class Cluster25Client: + def __init__( + self, + customer_id: Optional[str] = None, + customer_key: Optional[str] = None, + base_url: Optional[str] = None + ): + self.client_id = customer_id + self.client_secret = customer_key + self.base_url = base_url + self.current_token = self._get_cluster25_token() + self.headers = {"Authorization": f"Bearer {self.current_token}"} + + def _get_cluster25_token( + self + ) -> List[dict]: + payload = {"client_id": self.client_id, "client_secret": self.client_secret} + r = requests.post(url=f"{self.base_url}/token", json=payload, headers={"Content-Type": "application/json"}) + if r.status_code != 200: + raise Exception(f"Unable to retrieve the token from C25 platform, status {r.status_code}") + return r.json()["data"]["token"] + + def investigate( + self, + indicator + ) -> dict: + params = {'indicator': indicator.get('value')} + r = requests.get(url=f"{self.base_url}/investigate", params=params, headers=self.headers) + if r.status_code != 200: + return {'error': f"Unable to retrieve investigate result for indicator '{indicator.get('value')}' " + f"from C25 platform, status {r.status_code}"} + return r.json()["data"] + + +class C25CortexAnalyzer(Analyzer): + def __init__( + self + ): + Analyzer.__init__(self) + self.c25_api_key = self.get_param("config.client_key", None, "Missing Cluster25 api key") + self.c25_client_id = self.get_param("config.client_id", None, "Missing Cluster25 client id") + self.c25_base_url = self.get_param("config.base_url", None, "Missing Cluster25 base url") + self.c25_api_client = Cluster25Client(self.c25_client_id, self.c25_api_key, self.c25_base_url) + + def investigate( + self, + indicator: str + ) -> dict: + return self.c25_api_client.investigate({'value': indicator}) + + def summary( + self, + indicator_data: dict + ) -> dict: + taxonomies = [] + namespace = "C25" + level = 'info' + if indicator_data.get('indicator'): + taxonomies.append(self.build_taxonomy(level, namespace, "Indicator", indicator_data.get('indicator'))) + if indicator_data.get('indicator_type'): + taxonomies.append( + self.build_taxonomy(level, namespace, "Indicator Type", indicator_data.get('indicator_type'))) + if indicator_data.get('score'): + if indicator_data.get('score') < 50: + level = 'safe' + elif 50 <= indicator_data.get('score') < 80: + level = 'suspicious' + else: + level = 'malicious' + taxonomies.append(self.build_taxonomy(level, namespace, "Score", indicator_data.get('score'))) + if len(taxonomies) == 0: + taxonomies.append(self.build_taxonomy(level, namespace, 'Threat', 'Not found')) + + return {"taxonomies": taxonomies} + + def run( + self + ): + try: + indicator = self.get_param('data', None, 'Data is missing') + indicator_data = self.investigate(indicator) + if indicator_data: + self.report(indicator_data) + except Exception as e: + self.error(e) + + +if __name__ == '__main__': + C25CortexAnalyzer().run() diff --git a/analyzers/Cluster25/requirements.txt b/analyzers/Cluster25/requirements.txt new file mode 100644 index 000000000..0e5dd6b1b --- /dev/null +++ b/analyzers/Cluster25/requirements.txt @@ -0,0 +1,2 @@ +requests~=2.31.0 +cortexutils~=2.2.0 \ No newline at end of file diff --git a/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_GetDeviceVulnerabilities.py b/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_GetDeviceVulnerabilities.py index 9c4325a36..bffe8c154 100755 --- a/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_GetDeviceVulnerabilities.py +++ b/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_GetDeviceVulnerabilities.py @@ -19,8 +19,12 @@ def run(self): Analyzer.run(self) if self.data_type == 'hostname': try: + # Define the custom header + extra_headers = { + "User-Agent": "strangebee-thehive/1.0" + } auth = OAuth2(client_id=self.client_id, client_secret=self.client_secret) - hosts = Hosts(auth_object=auth) + hosts = Hosts(auth_object=auth, ext_headers=extra_headers) hostname = self.get_data() # Search for the device ID using the hostname @@ -35,7 +39,7 @@ def run(self): if device_ids: device_id = device_ids[0] # Get detailed asset information using the device ID - spotlight = SpotlightVulnerabilities(auth_object=auth) + spotlight = SpotlightVulnerabilities(auth_object=auth, ext_headers=extra_headers) host_vulns = spotlight.query_vulnerabilities_combined(parameters={"filter": f"aid:'{device_id}'+status:!'closed'"}) host_vulns = host_vulns["body"]["resources"] #print(host_vulns) diff --git a/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_Sandbox.py b/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_Sandbox.py index a76c8f9b2..54412b012 100755 --- a/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_Sandbox.py +++ b/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_Sandbox.py @@ -49,8 +49,12 @@ def run(self): with open(filepath, "rb") as sample: auth = OAuth2(client_id=self.client_id, client_secret=self.client_secret) - samples = SampleUploads(auth_object=auth) - sandbox = FalconXSandbox(auth_object=auth) + # Define the custom header + extra_headers = { + "User-Agent": "strangebee-thehive/1.0" + } + samples = SampleUploads(auth_object=auth, ext_headers=extra_headers) + sandbox = FalconXSandbox(auth_object=auth, ext_headers=extra_headers) response = samples.upload_sample(file_data=sample.read(), file_name=filename, comment=comment, diff --git a/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_getDeviceAlerts.py b/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_getDeviceAlerts.py index cbaf8b7f6..219008f1d 100755 --- a/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_getDeviceAlerts.py +++ b/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_getDeviceAlerts.py @@ -18,7 +18,11 @@ def run(self): if self.data_type == 'hostname': try: auth = OAuth2(client_id=self.client_id, client_secret=self.client_secret) - alerts = Alerts(auth_object=auth) + # Define the custom header + extra_headers = { + "User-Agent": "strangebee-thehive/1.0" + } + alerts = Alerts(auth_object=auth, ext_headers=extra_headers) hostname = self.get_data() message = "No alerts found." filtered_alert_list = [] diff --git a/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_getDeviceDetails.py b/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_getDeviceDetails.py index 322dd73de..f18c63875 100755 --- a/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_getDeviceDetails.py +++ b/analyzers/CrowdstrikeFalcon/CrowdstrikeFalcon_getDeviceDetails.py @@ -17,7 +17,11 @@ def run(self): if self.data_type == 'hostname': try: auth = OAuth2(client_id=self.client_id, client_secret=self.client_secret) - hosts = Hosts(auth_object=auth) + # Define the custom header + extra_headers = { + "User-Agent": "strangebee-thehive/1.0" + } + hosts = Hosts(auth_object=auth, ext_headers=extra_headers) hostname = self.get_data() # Search for the device ID using the hostname diff --git a/analyzers/ValidateObservable/README.md b/analyzers/ValidateObservable/README.md new file mode 100644 index 000000000..7eccc3ac0 --- /dev/null +++ b/analyzers/ValidateObservable/README.md @@ -0,0 +1,70 @@ +The **ValidateObservable** analyzer is designed to validate multiple observable datatypes. + +* _ip_ +* _domain_ +* _url_ +* _fqdn_ +* _mail_ +* _hash_ +* _filename_ +* _uri_path_ +* _user-agent_ + +## Supported Data Types / Features +1. **IP Addresses** + +- Validates individual IPs and CIDR ranges. +- Flags reserved, private, and loopback IPs with appropriate notes. + +2. **Domains** + +- Detects valid domain names. +- Flags domains using Punycode (e.g., xn--) as suspicious. +- Identifies unusual characters in domain names. + +3. **URLs** + +- Validates URLs with or without schemes. +- Flags URLs containing Punycode domains or unusual characters as suspicious. +- Detects malformed URLs. + +4. **Fully Qualified Domain Names (FQDNs)** + +- Validates FQDNs for proper structure and length. +- Flags FQDNs using Punycode and unusual characters as suspicious. + +5. **Emails** + +- Checks email structure for validity. +- Detects unusual characters in email addresses. +- Validates against length constraints. + +6. **File Hashes** + +- Validates MD5, SHA1, SHA256, and SHA512 hash formats. + +7. **Filenames** + +- Flags invalid characters in filenames (<, >, :, |, etc.). +- Detects multiple extensions (for example, .txt.exe) as suspicious. +- Identifies Unicode bidirectional override characters (U+202E, etc.) to prevent obfuscated extensions. + +8. **URI Paths** + +- Ensures paths start with / and are well-formed. + +9. **User Agents** + +- Checks for excessive length and control characters. + +## Special Features + +- **Unicode Detection**: + - Identifies Unicode bidirectional override characters (for example, U+202E) across domains, URLs, emails, filenames, and more. + - Flags their usage as suspicious to prevent obfuscation attacks. +- **Punycode Detection**: + - Flags internationalized domain names (IDNs) using xn-- prefix or uncommon characters. +- **Structured Output**: + - Returns valid, invalid, or suspicious statuses with detailed reasons. +- **Short reports**: + - Generates short reports to indicate the validation status and risk level : info (blue) or invalid / suspicious (orange). \ No newline at end of file diff --git a/analyzers/ValidateObservable/ValidateObservable.json b/analyzers/ValidateObservable/ValidateObservable.json new file mode 100644 index 000000000..c4ef5a08e --- /dev/null +++ b/analyzers/ValidateObservable/ValidateObservable.json @@ -0,0 +1,18 @@ +{ + "name": "ValidateObservable", + "version": "1.0", + "author": "nusantara-self, StrangeBee", + "url": "https://github.com/TheHive-Project/Cortex-Analyzers", + "license": "AGPL-V3", + "description": "Use regexes and libraries to indicate if observable is valid", + "dataTypeList": ["ip", "domain", "url", "fqdn", "mail", "hash", "filename", "uri_path", "user-agent"], + "baseConfig": "ValidateObservable", + "command": "ValidateObservable/ValidateObservable.py", + "config": { + "service": "validateObservable" + }, + "configurationItems": [], + "registration_required": false, + "subscription_required": false, + "free_subscription": false +} diff --git a/analyzers/ValidateObservable/ValidateObservable.py b/analyzers/ValidateObservable/ValidateObservable.py new file mode 100755 index 000000000..929c5ae3d --- /dev/null +++ b/analyzers/ValidateObservable/ValidateObservable.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 +# encoding: utf-8 + +from cortexutils.analyzer import Analyzer +import re +import ipaddress +from validators import url as validate_url_lib +from urllib.parse import urlparse +import idna + +class ValidateObservable(Analyzer): + def __init__(self): + Analyzer.__init__(self) + + def run(self): + # Validate based on data type + if self.data_type == 'ip': + result = self.validate_ip(self.get_data()) + elif self.data_type == 'domain': + result = self.validate_domain(self.get_data()) + elif self.data_type == 'url': + result = self.validate_url(self.get_data()) + elif self.data_type == 'fqdn': + result = self.validate_fqdn(self.get_data()) + elif self.data_type == 'mail': + result = self.validate_email(self.get_data()) + elif self.data_type == 'hash': + result = self.validate_hash(self.get_data()) + elif self.data_type == 'filename': + result = self.validate_filename(self.get_data()) + elif self.data_type == 'uri_path': + result = self.validate_uri_path(self.get_data()) + elif self.data_type == 'user-agent': + result = self.validate_user_agent(self.get_data()) + else: + self.error(f"Unsupported data type: {self.data_type}") + + self.report(result) + + def contains_bidi_override(self, value): + bidi_override_chars = ["\u202E", "\u202D", "\u200E", "\u200F", "\u2066", "\u2067"] + for char in bidi_override_chars: + if char in value: + return f"Contains Unicode bidirectional override character U+{ord(char):04X}" + return None + + def validate_ip(self, ip): + try: + + if "/" in ip: # CIDR range + ipaddress.ip_network(ip, strict=False) + return { + "status": "valid", + "type": "IP range", + "value": ip + } + else: # Single IP + ip_obj = ipaddress.ip_address(ip) + if ip_obj.is_loopback: + return { + "status": "valid", + "type": "IP address", + "value": ip, + "note": "Loopback IP address" + } + elif ip_obj.is_private: + return { + "status": "valid", + "type": "IP address", + "value": ip, + "note": "Private IP address" + } + elif ip_obj.is_reserved: + return { + "status": "valid", + "type": "IP address", + "value": ip, + "note": "Reserved IP address" + } + else: + return { + "status": "valid", + "type": "IP address", + "value": ip + } + except ValueError: + return { + "status": "invalid", + "type": "IP address", + "value": ip + } + + def validate_domain(self, domain): + try: + # Convert non-ASCII domains to Punycode + punycode_domain = idna.encode(domain).decode() + + # Check for Punycode (IDN) and unusual characters + if domain.startswith("xn--"): + return { + "status": "suspicious", + "type": "Domain", + "value": domain, + "reason": "Domain uses Punycode, which may indicate an internationalized domain name (IDN)" + } + + # Validate the domain structure + domain_regex = r'^(?!-)([A-Za-z0-9-]{1,63}(? 255: + return { + "status": "invalid", + "type": "Domain", + "value": domain, + "reason": "Exceeds maximum length of 255 characters" + } + if re.match(domain_regex, punycode_domain): + if re.search(r"[^a-zA-Z0-9.-]", domain): + return { + "status": "suspicious", + "type": "Domain", + "value": domain, + "reason": "Domain is valid but contains IDN or unusual characters" + } + return { + "status": "valid", + "type": "Domain", + "value": domain + } + else: + return { + "status": "invalid", + "type": "Domain", + "value": domain + } + except idna.IDNAError: + return { + "status": "invalid", + "type": "Domain", + "value": domain, + "reason": "Invalid internationalized domain name" + } + + + + def validate_url(self, url): + bidi_check = self.contains_bidi_override(url) + if bidi_check: + return { + "status": "suspicious", + "type": "URL", + "value": url, + "reason": bidi_check + } + + parsed = urlparse(url) + if not parsed.scheme and not parsed.netloc: + # Validate as a domain if scheme and netloc are missing + return self.validate_domain(url) + + if all([parsed.scheme, parsed.netloc]): + if parsed.netloc.startswith("xn--"): + return { + "status": "suspicious", + "type": "URL", + "value": url, + "reason": "URL contains a Punycode domain, which may indicate an internationalized domain name (IDN)" + } + + if re.search(r"[^a-zA-Z0-9:/?&=._-]", url): + return { + "status": "suspicious", + "type": "URL", + "value": url, + "reason": "Contains unusual characters" + } + return { + "status": "valid", + "type": "URL", + "value": url + } + return { + "status": "invalid", + "type": "URL", + "value": url, + "reason": "Malformed or missing scheme/netloc" + } + + def validate_fqdn(self, fqdn): + fqdn_regex = ( + r'^(?!-)([A-Za-z0-9-]{1,63}(? 255: + return { + "status": "invalid", + "type": "FQDN", + "value": fqdn, + "reason": "Exceeds maximum length of 255 characters" + } + if fqdn.startswith("xn--"): + return { + "status": "suspicious", + "type": "FQDN", + "value": fqdn, + "reason": "FQDN uses Punycode, which may indicate an internationalized domain name (IDN)" + } + if re.match(fqdn_regex, fqdn): + if re.search(r"[^a-zA-Z0-9.-]", fqdn): + return { + "status": "suspicious", + "type": "FQDN", + "value": fqdn, + "reason": "Contains unusual characters" + } + return { + "status": "valid", + "type": "FQDN", + "value": fqdn + } + else: + return { + "status": "invalid", + "type": "FQDN", + "value": fqdn + } + + def validate_email(self, email): + bidi_check = self.contains_bidi_override(email) + if bidi_check: + return { + "status": "suspicious", + "type": "Email", + "value": email, + "reason": bidi_check + } + + email_regex = ( + r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' + ) + if len(email) > 254: + return { + "status": "invalid", + "type": "Email", + "value": email, + "reason": "Exceeds maximum length of 254 characters" + } + if re.match(email_regex, email): + if re.search(r"[^a-zA-Z0-9@._%+-]", email): + return { + "status": "suspicious", + "type": "Email", + "value": email, + "reason": "Contains unusual characters" + } + return { + "status": "valid", + "type": "Email", + "value": email + } + else: + return { + "status": "invalid", + "type": "Email", + "value": email + } + + def validate_hash(self, hash_value): + hash_regex = { + "MD5": r"^[a-fA-F0-9]{32}$", + "SHA1": r"^[a-fA-F0-9]{40}$", + "SHA256": r"^[a-fA-F0-9]{64}$", + "SHA512": r"^[a-fA-F0-9]{128}$" + } + for hash_type, regex in hash_regex.items(): + if re.match(regex, hash_value): + return { + "status": "valid", + "type": f"{hash_type} Hash", + "value": hash_value + } + return { + "status": "invalid", + "type": "Hash", + "value": hash_value, + "reason": "Does not match known hash formats (supported types: MD5, SHA1, SHA256, SHA512)" + } + + def validate_filename(self, filename): + bidi_check = self.contains_bidi_override(filename) + if bidi_check: + return { + "status": "suspicious", + "type": "Filename", + "value": filename, + "reason": bidi_check + } + + invalid_chars = r"[<>:\"/\\|?*]" + if len(filename) > 255: + return { + "status": "invalid", + "type": "Filename", + "value": filename, + "reason": "Exceeds maximum length of 255 characters" + } + if re.search(invalid_chars, filename): + return { + "status": "invalid", + "type": "Filename", + "value": filename, + "reason": "Contains invalid characters" + } + if re.search(r"\.\w{2,4}(\.\w{2,4})", filename): + return { + "status": "suspicious", + "type": "Filename", + "value": filename, + "reason": "Contains multiple extensions that may confuse users" + } + return { + "status": "valid", + "type": "Filename", + "value": filename + } + + + def validate_uri_path(self, uri_path): + parsed = urlparse(uri_path) + if parsed.path and parsed.path.startswith("/"): + return { + "status": "valid", + "type": "URI Path", + "value": uri_path + } + return { + "status": "invalid", + "type": "URI Path", + "value": uri_path, + "reason": "Does not start with '/' or is malformed" + } + + def validate_user_agent(self, user_agent): + if len(user_agent) > 512: + return { + "status": "invalid", + "type": "User-Agent", + "value": user_agent, + "reason": "Exceeds maximum length of 512 characters" + } + if re.search(r"[\x00-\x1F\x7F]", user_agent): + return { + "status": "invalid", + "type": "User-Agent", + "value": user_agent, + "reason": "Contains control characters" + } + return { + "status": "valid", + "type": "User-Agent", + "value": user_agent + } + + def summary(self, raw): + taxonomies = [] + namespace = "ValidateObs" + predicate = self.data_type + + # Determine level based on status + status = raw.get("status") + if status == "valid": + level = "info" + elif status == "suspicious": + level = "suspicious" + else: + level = "suspicious" + + # Build taxonomy based on validation result + taxonomies.append( + self.build_taxonomy( + level, namespace, predicate, status) + ) + return {"taxonomies": taxonomies} + +if __name__ == "__main__": + ValidateObservable().run() diff --git a/analyzers/ValidateObservable/requirements.txt b/analyzers/ValidateObservable/requirements.txt new file mode 100644 index 000000000..dd8e4afa9 --- /dev/null +++ b/analyzers/ValidateObservable/requirements.txt @@ -0,0 +1,3 @@ +cortexutils +validators +idna \ No newline at end of file diff --git a/responders/CrowdstrikeFalcon/CrowdstrikeFalconHosts.py b/responders/CrowdstrikeFalcon/CrowdstrikeFalconHosts.py index cf0a6b4ae..9e751ca37 100755 --- a/responders/CrowdstrikeFalcon/CrowdstrikeFalconHosts.py +++ b/responders/CrowdstrikeFalcon/CrowdstrikeFalconHosts.py @@ -14,8 +14,12 @@ def run(self): Responder.run(self) hostname = self.get_param("data.data", None) #self.report({'message': f"Host {device_name}"}) + # Define the custom headers + extra_headers = { + "User-Agent": "strangebee-thehive/1.0" + } auth = OAuth2(client_id=self.client_id, client_secret=self.client_secret) - hosts = Hosts(auth_object=auth) + hosts = Hosts(auth_object=auth, ext_headers=extra_headers) # Search for the device ID using the hostname if self.service == "unhide_host": @@ -44,4 +48,4 @@ def operations(self, raw): return operations_list if __name__ == '__main__': - CrowdstrikeFalconHosts().run() \ No newline at end of file + CrowdstrikeFalconHosts().run() diff --git a/responders/CrowdstrikeFalcon/CrowdstrikeFalconIOC.py b/responders/CrowdstrikeFalcon/CrowdstrikeFalconIOC.py index 0566fbe57..174a978da 100755 --- a/responders/CrowdstrikeFalcon/CrowdstrikeFalconIOC.py +++ b/responders/CrowdstrikeFalcon/CrowdstrikeFalconIOC.py @@ -78,8 +78,12 @@ def run(self): case_id = self.get_param("data.case.id", None, "Can't get case ID") description = f"Pushed from TheHive - {case_title} - {case_id}" + # Define the custom headers + extra_headers = { + "User-Agent": "strangebee-thehive/1.0" + } # Create the IOC service object - ioc = IOC(client_id=self.client_id, client_secret=self.client_secret) + ioc = IOC(client_id=self.client_id, client_secret=self.client_secret, ext_headers=extra_headers) # Determine if the IOC applies globally or to specific host groups ioc_kwargs = { @@ -117,7 +121,13 @@ def run(self): filter = f"_all:~'{ioc_value}'" - ioc = IOC(client_id=self.client_id, client_secret=self.client_secret) + + # Define the custom headers + extra_headers = { + "User-Agent": "strangebee-thehive/1.0" + } + # Create the IOC service object + ioc = IOC(client_id=self.client_id, client_secret=self.client_secret, ext_headers=extra_headers) # Search for the IOC by value response = ioc.indicator_search(filter=filter,offset=0, limit=200) @@ -143,4 +153,4 @@ def run(self): if __name__ == '__main__': - CrowdstrikeFalconIOC().run() \ No newline at end of file + CrowdstrikeFalconIOC().run() diff --git a/responders/CrowdstrikeFalcon/CrowdstrikeFalconSync.py b/responders/CrowdstrikeFalcon/CrowdstrikeFalconSync.py index 6dcc2cea2..83e5d111b 100755 --- a/responders/CrowdstrikeFalcon/CrowdstrikeFalconSync.py +++ b/responders/CrowdstrikeFalcon/CrowdstrikeFalconSync.py @@ -11,11 +11,13 @@ def __init__(self): self.service = self.get_param("config.service", None) self.custom_field_name_alert_id = self.get_param("config.custom_field_name_alert_id") self.custom_field_name_incident_id = self.get_param("config.custom_field_name_incident_id") - self.alert_client = Alerts(client_id=self.client_id, client_secret=self.client_secret) - self.incident_client = Incidents(client_id=self.client_id, client_secret=self.client_secret) def run(self): if self.service == "sync": + # Define the custom headers + extra_headers = { + "User-Agent": "strangebee-thehive/1.0" + } #data = self.get_param("data", None, "Can't get case ID") current_stage = self.get_param("data.stage", None, "Can't get case or alert stage") detection_id = self.get_param(f"data.customFieldValues.{self.custom_field_name_alert_id}", None) @@ -48,6 +50,7 @@ def run(self): # Update the CrowdStrike alert status if detection_id: + alert_client = Alerts(client_id=self.client_id, client_secret=self.client_secret, ext_headers=extra_headers) # Determine the corresponding CrowdStrike alert status cs_status_alert = status_mapping_alert[current_stage] if isinstance(detection_id,str): @@ -62,11 +65,12 @@ def run(self): } ] } - alert_response = self.alert_client.update_alerts_v3(body=alert_body) + alert_response = alert_client.update_alerts_v3(body=alert_body) alert_status_code = alert_response.get('status_code', None) if incident_id: + incident_client = Incidents(client_id=self.client_id, client_secret=self.client_secret, ext_headers=extra_headers) # Determine the corresponding CrowdStrike incident status cs_status_incident = status_mapping_incident[current_stage] if isinstance(incident_id,str): @@ -82,7 +86,7 @@ def run(self): ] } - incident_response = self.incident_client.perform_incident_action(body=incident_body) + incident_response = incident_client.perform_incident_action(body=incident_body) incident_status_code = incident_response.get('status_code', None) @@ -110,4 +114,4 @@ def run(self): self.report({"message": final_message}) if __name__ == '__main__': - CrowdstrikeFalconSync().run() \ No newline at end of file + CrowdstrikeFalconSync().run() diff --git a/thehive-templates/Axur_1_0/long.html b/thehive-templates/Axur_1_0/long.html new file mode 100644 index 000000000..616858389 --- /dev/null +++ b/thehive-templates/Axur_1_0/long.html @@ -0,0 +1,71 @@ +
+
+ Axur IOC Search Results +
+
+
+
+ {{result.source}} +
+
+
+
Source
+
{{result.source || "-"}}
+ +
Hits
+
{{result.hits || "-"}}
+ +
Score
+
{{result.score || "-"}}
+
+ +
+
+ +
+
+
Tags
+
{{context.tags.join(', ') || "-"}}
+ +
Detection
+
{{context.detection || "-"}}
+
+
+ +
+
+
Content
+
{{context.content || "-"}}
+ +
Detection
+
{{context.detection || "-"}}
+
+
+ +
+
+
Risk Level
+
{{context.riskLevel || "-"}}
+ +
Collector Name
+
{{context['collector-name'] || "-"}}
+ +
Detection
+
{{context.detection || "-"}}
+
+
+
+
+
+
+
+ + +
+
+ Error +
+
+ {{content.errorMessage}} +
+
diff --git a/thehive-templates/Axur_1_0/short.html b/thehive-templates/Axur_1_0/short.html new file mode 100644 index 000000000..5fc0dabfb --- /dev/null +++ b/thehive-templates/Axur_1_0/short.html @@ -0,0 +1,3 @@ + + {{t.namespace}}:{{t.predicate}}="{{t.value}}" + diff --git a/thehive-templates/Cluster25_1_0/long.html b/thehive-templates/Cluster25_1_0/long.html new file mode 100644 index 000000000..78883bfae --- /dev/null +++ b/thehive-templates/Cluster25_1_0/long.html @@ -0,0 +1,402 @@ +
+
+ General Info +
+
+
Indicator:
+
{{content.indicator}}
+
Indicator Type:
+
{{content.indicator_type}}
+
First Seen:
+
{{content.first_seen}}
+
Last Seen:
+
{{content.last_seen}}
+
Whitelisted:
+
{{content.whitelisted}}
+
Score:
+
{{content.score}}/100
+
Harmless:
+
{{content.stats.harmless}}/100
+
Malicious:
+
{{content.stats.malicious}}/100
+
Suspicious:
+
{{content.stats.suspicious}}/100
+
Undetected:
+
{{content.stats.undetected}}/100
+
Is Known:
+
{{content.is_known}}
+
Actors:
+
{{actor.name}}
+
Tags:
+
{{tag.name}}
+
Related Indicators By File:
+
{{indicator.value}} +
+
Related Indicators By Content:
+
{{indicator.value}} +
+
Related Indicators Contexts:
+
{{contex.title}}
+
Created:
+
{{content.created_dt}}
+
Modified:
+
{{content.modified_dt}}
+
Attacker Activities:
+
{{activity.actorip}}
+
Targeted Sectors:
+
{{sector}}
+
Targeted Countries:
+
{{country}}
+
Guessed Types:
+
{{type}}
+
+
+
+
+ File Info +
+
+
Md5:
+
{{content.file_info.md5}}
+
Sha1:
+
{{content.file_info.sha1}}
+
Sha256:
+
{{content.file_info.md5}}
+
Ssdeep:
+
{{content.file_info.ssdeep}}
+
Type Description:
+
{{content.file_info.type_description}}
+
Names:
+
{{name}} +
+
Size:
+
{{content.file_info.size}}
+
+
+ Cve Info +
+
+
Id:
+
{{content.cve_info.md5}}
+
Published Date:
+
{{content.cve_info.published_date}}
+
Last Modified Date:
+
{{content.cve_info.last_modified_date}}
+
Description:
+
{{content.cve_info.description}}
+
References:
+
{{reference}}
+
+
+ Asn Info +
+
+
Organization:
+
{{content.asn_info.organization}}
+
Name:
+
{{content.asn_info.name}}
+
Registry:
+
{{content.asn_info.registry}}
+
Registered Country:
+
{{content.asn_info.registered_country}}
+
Registered Country Name:
+
{{content.asn_info.registered_country_name}}
+
Registration Date
+
{{content.asn_info.registration_dt}}
+
Registration Last Change
+
{{content.asn_info.registration_dt}}
+
Total Ipv4:
+
{{content.asn_info.total_ipv4}}
+
+
+ Bitcoin Address Info +
+
+
Total Received:
+
{{content.btcaddress_info.total_received}}
+
Total Sent:
+
{{content.btcaddress_info.total_sent}}
+
Balance:
+
{{content.btcaddress_info.balance}}
+
Transaction Number:
+
{{content.btcaddress_info.transation_number}}
+
Report Count:
+
{{content.btcaddress_info.report_count}}
+
Report First Seen
+
{{content.btcaddress_info.report_first_seen}}
+
Report Last Seen
+
{{content.btcaddress_info.report_last_seen}}
+
Recent:
+
Abuse type: {{recent.abuse_type}} Description: {{recent.description}} +
+
+
+ Family Info +
+
+
Family Name:
+
{{content.family_info.family_name}}
+
Hits:
+
On {{hit.seen_dt}} there were: {{hit.hits}} hits +
+
+
+ Communicating Files +
+
+ +
Communicating File {{$index}}
+
Md5:
{{file.md5}}
+
Sha1:
{{file.sha1}}
+
Sha256:
{{file.md5}}
+
Ssdeep:
{{file.ssdeep}}
+
Type Description:
{{file.type_description}}
+
Names:
{{name}}
+
Size:
{{file.size}}
+
+
+
+ Contacted Ips +
+
+ +
Contacted Ip {{$index}}
+
Asn:
{{ip.asn}}
+
As Owner:
{{ip.as_owner}}
+
country:
{{ip.country}}
+
+
+
+ Contacted Domains +
+
+ +
Contacted Domain {{$index}}
+
Registrar:
{{domain.registrar}}
+
+
+ +
+ Contacted Urls +
+
+ +
Contacted Url {{$index}}
+
Value:
{{url.value}}
+
+
+
+ Dropped Files +
+
+ +
Communicating File {{$index}}
+
Md5:
{{file.md5}}
+
Sha1:
{{file.sha1}}
+
Sha256:
{{file.md5}}
+
Ssdeep:
{{file.ssdeep}}
+
Type Description:
{{file.type_description}}
+
Names:
{{name}}
+
Size:
{{file.size}}
+
+
+
+ Passive Dns +
+
+ +
Resolution {{$index}}
+
Record Name:
{{res.record_name}}
+
Record Value:
{{res.record_value}}
+
Record Type:
{{res.record_type}}
+
First Seen:
{{res.first_seen}}
+
Last Seen:
{{res.last_seen}}
+
Country Name:
{{res.country_name}}
+
+
+
+ Who Is +
+
+
Ip:
+
{{content.whois.ip}}
+
Created Date:
+
{{content.whois.created_date}}
+
Updated Date:
+
{{content.whois.updated_date}}
+
Expires Date:
+
{{content.whois.expires_date}}
+
Registrant Name:
+
{{content.whois.registrant.name}}
+
Registrant Organization:
+
{{content.whois.registrant.organization}}
+
Registrant Street1:
+
{{content.whois.registrant.street1}}
+
Registrant Street2:
+
{{content.whois.registrant.street2}}
+
Registrant City:
+
{{content.whois.registrant.city}}
+
Registrant State:
+
{{content.whois.registrant.state}}
+
Registrant Country:
+
{{content.whois.registrant.country}}
+
Registrant Postal Code:
+
{{content.whois.registrant.postal_code}}
+
Registrant Raw Text:
+
{{content.whois.registrant.raw_text}}
+
Registrar Name:
+
{{content.whois.registrar_name}}
+
Name Servers Hostnames:
+
{{hostname}}
+
Name Servers Ips:
+
{{ip}}
+
Email Provider:
+
{{content.whois.email_provider}}
+
Email Registrant:
+
{{content.whois.email_registrant}}
+
Status:
+
{{content.whois.status}}
+ +
+
+ Botnets +
+
+
Total:
+
{{content.intelligence.botnets.total}}
+
Events:
+ +
Event {{$index}}
+
Name:
{{event.name}}
+
Country:
{{event.country}}
+
Date Collect:
{{event.date_collect}}
+
Created:
{{event.created_dt}}
+
Domain:
{{event.domain}}
+
GeoIp:
{{event.geoip.ip}}
+
Ip:
{{event.ip}}
+
Isp:
{{event.isp}}
+
Source:
{{event.source}}
+
System:
{{event.system}}
+
Password:
{{event.password}}
+
Prefix:
{{event.prefix}}
+
Solved:
{{event.solved}}
+
Username:
{{event.username}}
+
Country Name:
{{res.country_name}}
+
+
+
+ Darknet Bots +
+
+ +
Bot {{$index}}
+
Bot Name:
{{bot.bot_name}}
+
Bot Country:
{{bot.bot_country}}
+
Bot Installed:
{{bot.bot_installed}}
+
Bot OS:
{{bot.bot_os}}
+
Forum Name:
{{bot.forum_name}}
+
Resource Name:
{{thread.resource_name}}
+
Created:
{{bot.created_dt}}
+
Keyword
: +
{{bot.keyword}}
+
Solved:
{{thread.solved}}
+
Type:
{{thread.type}}
+
Url:
{{thread.url}}
+
+
+
+ Darknet Threads +
+
+ +
Thread {{$index}}
+
Name:
{{thread.name}}
+
Author:
{{thread.author}}
+
Created:
{{thread.created_dt}}
+
Forum Name:
{{thread.forum_name}}
+
Keyword:
{{thread.keyword}}
+
Post Content:
{{thread.post_content}}
+
Resource Name:
{{thread.resource_name}}
+
Solved:
{{thread.solved}}
+
Type:
{{thread.type}}
+
Url:
{{thread.url}}
+
+
+
+ Darknet Sellers +
+
+ +
Seller {{$index}}
+
Account Country:
{{seller.account_country}}
+
Account Description:
{{seller.account_description}}
+
Account Resource:
{{seller.account_resource}}
+
Account Seller:
{{seller.account_seller}}
+
Created
{{seller.created_dt}}
+
Forum Name:
{{seller.forum_name}}
+
Keyword:
{{seller.keyword}}
+
Resource Name:
{{seller.resource_name}}
+
Solved:
{{thread.solved}}
+
Type:
{{thread.type}}
+
+
+
+ Credentials +
+
+ +
Total:
+
{{content.intelligence.botnets.total}}
+
Events:
+ +
Credential {{$index}}
+
Url:
{{credential.url}}
+
Fqdn:
{{credential.fqdn}}
+
Source:
{{credential.source}}
+
Username:
{{credential.username}}
+
Password
: +
{{credential.password}}
+
Dumpdate:
{{credential.dumpdate}}
+
Created
{{credential.created_dt}}
+
Keyword:
{{credential.keyword}}
+
File Path:
{{credential.file_path}}
+
Ip
{{credential.ip}}
+
Solved
{{credential.solved}}
+
+
+
+ Dns Resolutions +
+
+ +
Resolution {{$index}}
+
Domain:
{{res.domain}}
+
Country Code:
{{res.country_code}}
+
QType:
{{res.qtype}}
+
Timestamp:
{{res.timestamp}}
+
+
+ +
\ No newline at end of file diff --git a/thehive-templates/ValidateObservable_1_0/long.html b/thehive-templates/ValidateObservable_1_0/long.html new file mode 100644 index 000000000..49671cab2 --- /dev/null +++ b/thehive-templates/ValidateObservable_1_0/long.html @@ -0,0 +1,55 @@ + +
+
+ ValidateObservable Result +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Type{{ content["type"] }}
Value{{ content["value"] }}
Status + + {{ content["status"] | uppercase }} + +
Reason{{ content["reason"] }}
Note{{ content["note"] }}
+
+
+ + +
+
+ No Result +
+
+

No output is available for the "ValidateObservable" analyzer.

+
+
\ No newline at end of file