From a4ffcd55564f314391fb229cdc2e60e255b46f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rebollo=20P=C3=A9rez?= Date: Fri, 3 May 2024 12:45:10 +0100 Subject: [PATCH 1/2] fix: rstrip vulnerability fields --- .../end_to_end/remote_operations_handler.py | 8 +- .../end_to_end/vulnerability_detector.py | 441 ++++++++---------- 2 files changed, 190 insertions(+), 259 deletions(-) diff --git a/deps/wazuh_testing/wazuh_testing/end_to_end/remote_operations_handler.py b/deps/wazuh_testing/wazuh_testing/end_to_end/remote_operations_handler.py index 8631cfc354..8a60792d28 100644 --- a/deps/wazuh_testing/wazuh_testing/end_to_end/remote_operations_handler.py +++ b/deps/wazuh_testing/wazuh_testing/end_to_end/remote_operations_handler.py @@ -222,12 +222,8 @@ def get_package_system(host: str, host_manager: HostManager) -> str: return system -def get_vulnerability_alerts( - host_manager: HostManager, - agent_list, - packages_data: List, - greater_than_timestamp: str = "", -) -> Dict: +def get_vulnerability_alerts(host_manager: HostManager, agent_list, packages_data: List, + greater_than_timestamp: str = "") -> Dict: alerts = get_vulnerabilities_from_alerts_by_agent( host_manager, agent_list, greater_than_timestamp=greater_than_timestamp ) diff --git a/deps/wazuh_testing/wazuh_testing/end_to_end/vulnerability_detector.py b/deps/wazuh_testing/wazuh_testing/end_to_end/vulnerability_detector.py index 2850b1e23f..c1f94fba45 100644 --- a/deps/wazuh_testing/wazuh_testing/end_to_end/vulnerability_detector.py +++ b/deps/wazuh_testing/wazuh_testing/end_to_end/vulnerability_detector.py @@ -5,11 +5,10 @@ Functions: - load_packages_metadata: Load packages metadata from the packages.json file. - - check_vuln_state_index: Check vulnerability state index for a host. - - get_indexed_vulnerabilities_by_agent: Get indexed vulnerabilities by agent. - - check_vuln_alert_indexer: Check vulnerability alerts in the indexer for a host. - - parse_vulnerability_detector_alerts: Parse vulnerability detector alerts. - - check_vuln_state_consistency: Check vulnerability state consistency. + - get_vulnerability_detector_alerts: Parse vulnerability detector alerts index. + - parse_vulnerabilities_from_alerts: Parse vulnerabilities from the vulnerability detector alerts. + - parse_vulnerabilities_from_states: Parse vulnerabilities from the vulnerability state index. + - get_vulnerabilities_from_alerts_by_agent: Get vulnerabilities from the alerts by agent. Copyright (C) 2015, Wazuh Inc. @@ -35,6 +34,15 @@ Vulnerability = namedtuple('Vulnerability', ['cve', 'package_name', 'package_version', 'architecture']) +def normalize_architecture(architecture: str) -> str: + if architecture == 'amd64': + architecture = 'x86_64' + elif architecture == 'aarch64': + architecture = 'arm64' + + return architecture + + def load_packages_metadata() -> Dict: """ Load packages metadata from the packages.json file. @@ -70,65 +78,50 @@ def load_packages_metadata() -> Dict: return packages_data -def check_vuln_state_index(host_manager: HostManager, host: str, package: Dict[str, Dict], - current_datetime: str = "") -> List: - """ - Check vulnerability state index for a host. This function checks if the vulnerability state index contains the - expected vulnerabilities for a host. It returns a dictionary containing the expected alerts not found. - - Args: - host_manager (HostManager): An instance of the HostManager class containing information about hosts. - host (str): Host name. - package (dict): Dictionary containing package data. - current_datetime (str): Datetime to filter the vulnerability state index. - """ - indexer_user, indexer_password = host_manager.get_indexer_credentials() - filter = create_vulnerability_states_indexer_filter(target_agent=host, greater_than_timestamp=current_datetime) - index_vuln_state_content = get_indexer_values(host_manager, index=WAZUH_STATES_VULNERABILITIES_INDEXNAME, - filter=filter, - credentials={'user': indexer_user, - 'password': indexer_password})['hits']['hits'] - expected_alerts_not_found = [] - - logging.info(f"Checking vulnerability state index {package}") - vulnerabilities = package['CVE'] - - for vulnerability in vulnerabilities: - found = False - vulnerability_case = { - 'agent': host, - 'cve': vulnerability, - 'package_name': package['package_name'], - 'package_version': package['package_version'] - } - - for index_vuln in index_vuln_state_content: - state_agent = index_vuln['_source']['agent']['name'] - state_cve = index_vuln["_source"]['vulnerability']['id'] - state_package_name = index_vuln['_source']['package']['name'] - state_package_version = index_vuln['_source']['package']['version'] - - if state_agent == host and state_cve == vulnerability \ - and state_package_name == package['package_name'] and \ - state_package_version == package['package_version']: - found = True - break - - if not found: - expected_alerts_not_found.append(vulnerability_case) - - return expected_alerts_not_found - - def get_vulnerability_detector_alerts(alerts: List) -> Dict: - """ - Parse vulnerability detector alerts. + """Filter vulnerability detector alerts from alert index list. Args: - alerts (list): List of alerts. + alerts (list): List of alerts index. + + Example of alerts: + [ + { + "_index":"wazuh-alerts-4.x-2024.04.22", + "_id":"YqpJBY8BJodbzcVedMR2", + "_score":4.6972857, + ... + "data":{ + "vulnerability":{ + "severity":"Medium", + "package":{ + "condition":"Package less than 0.7.0", + "name":"http-proxy", + "source":" ", + "version":"0.5.9", + "architecture":" " + }, + ... + } + }, + "rule":{ + ... + "description":"CVE-2017-16014 affects http-proxy", + ... + "timestamp":"2024-04-22T10:10:45.737+0000" + } + } + ]] Returns: dict: Dictionary containing the alerts by agent. + + Example of return value: + { + 'affected': [{ "_index":"wazuh-alerts-4.x-2024.04.22", + "_id":"YqpJBY8BJodbzcVedMR2", "_score":4.6972857, ... }] + 'mitigated': [] + } """ vulnerability_detector_alerts = {} vulnerability_detector_alerts['affected'] = [] @@ -137,7 +130,6 @@ def get_vulnerability_detector_alerts(alerts: List) -> Dict: vuln_affected_regex = REGEX_PATTERNS['vuln_affected']['regex'] vuln_mitigated_regex = REGEX_PATTERNS['vuln_mitigated']['regex'] - # Parse affected vuln alerts for alert in alerts: if re.match(vuln_affected_regex, alert['_source']['rule']['description']): vulnerability_detector_alerts['affected'].append(alert) @@ -153,225 +145,128 @@ def parse_vulnerabilities_from_alerts(vulnerabilities_alerts: List) -> List: Args: vulnerabilities_alerts (list): List of vulnerabilities from the vulnerability detector alerts. + Example of vulnerabilities_alerts: + [ + { + "_index":"wazuh-alerts-4.x-2024.04.22", + "_id":"YqpJBY8BJodbzcVedMR2", + "_score":4.6972857, + ... + "data":{ + "vulnerability":{ + "severity":"Medium", + "package":{ + "condition":"Package less than 0.7.0", + "name":"http-proxy", + "source":" ", + "version":"0.5.9", + "architecture":" " + }, + ... + } + }, + ... + } + ] + Returns: list: List of vulnerabilities sorted by cve, package_name, package_version, and architecture. + + Example of return value: + [ + Vulnerability(cve='CVE-2021-25804', package_name='http-proxy', package_version='0.5.9', architecture=''), + ] """ + def parse_vulnerability_from_alert(alert): + vulnerability_data = alert.get('_source', {}).get('data', {}).get('vulnerability', {}) + package_data = vulnerability_data.get('package', {}) + architecture = normalize_architecture(package_data.get('architecture', '')) + + vulnerability = Vulnerability( + cve=vulnerability_data.get('cve', '').rstrip(), + package_name=package_data.get('name', '').rstrip(), + package_version=package_data.get('version', '').rstrip(), + architecture=architecture.rstrip() + ) + + return vulnerability + vulnerabilities = [] for alert in vulnerabilities_alerts: try: - architecture = alert['_source']['data']['vulnerability']['package']['architecture'] if \ - 'vulnerability' in alert['_source']['data'] and \ - 'package' in alert['_source']['data']['vulnerability'] and \ - 'architecture' in alert['_source']['data']['vulnerability']['package'] else "" - - if architecture == 'amd64': - architecture = 'x86_64' - elif architecture == 'aarch64': - architecture = 'arm64' - - vulnerability = Vulnerability( - cve=alert['_source']['data']['vulnerability']['cve'], - package_name=alert['_source']['data']['vulnerability']['package']['name'], - package_version=alert['_source']['data']['vulnerability']['package']['version'], - architecture=architecture - ) + vulnerability = parse_vulnerability_from_alert(alert) vulnerabilities.append(vulnerability) + except KeyError as e: logging.error(f"Error parsing vulnerability: {alert}: {str(e)}") + raise e vulnerabilities = sorted(vulnerabilities, key=lambda x: (x.cve, x.package_name, x.package_version, x.architecture)) return vulnerabilities -def get_indexed_vulnerabilities_by_agent(indexed_vulnerabilities) -> Dict: - """Get indexed vulnerabilities by agent. - - Args: - indexed_vulnerabilities (dict): Dictionary containing the indexed vulnerabilities. - - Returns: - dict: Dictionary containing the indexed vulnerabilities by agent. - """ - vulnerabilities_by_agent = {} - for vulnerabilities_state in indexed_vulnerabilities['hits']['hits']: - if 'agent' in vulnerabilities_state['_source']: - agent = vulnerabilities_state['_source']['agent']['name'] - if agent not in vulnerabilities_by_agent: - vulnerabilities_by_agent[agent] = [] - - vulnerabilities_by_agent[agent].append(vulnerabilities_state) - - return vulnerabilities_by_agent - - -def check_vuln_alert_indexer(vulnerabilities_alerts: Dict, host: str, package: Dict[str, Dict], - current_datetime: str = '') -> List: - """ - Check vulnerability alerts in the indexer for a host. - - Args: - vulnerabilities_alerts (Dict): Dictionary containing the indexed vulnerabilities by agent. - host (str): Host name. - package (dict): Dictionary containing package data. - vuln_mitigated (bool): Indicates if the vulnerability is mitigated. - vulnerability_data (dict): Dictionary containing vulnerability data. - - Returns: - list: List of vulnerability alerts. - """ - logging.info(f"Checking vulnerability alerts in the indexer {package}") - - # Get CVE affects alerts for all agents - if host in vulnerabilities_alerts: - triggered_alerts = vulnerabilities_alerts[host] - else: - triggered_alerts = [] - - expected_alerts_not_found = [] - - for cve in package['CVE']: - logging.info(f"Checking vulnerability: {cve}") - - package_name = package['package_name'] - package_version = package['package_version'] - - found = False - - for triggered_alert in triggered_alerts: - alert_package_name = triggered_alert['_source']['data']['vulnerability']['package']["name"] - alert_package_version = \ - triggered_alert['_source']['data']['vulnerability']['package']['version'] - alert_cve = triggered_alert['_source']['data']['vulnerability']['cve'] - - if alert_cve == cve and alert_package_name == package_name and \ - alert_package_version == package_version: - found = True - break - - if not found: - logging.info(f"Vulnerability not found: {cve} for package {package} {package_version}") - expected_alerts_not_found.append({'CVE': cve, 'PACKAGE_NAME': package_name, - 'PACKAGE_VERSION': package_version}) - - return expected_alerts_not_found - - -def check_vuln_state_consistency(vulnerabilities_alerts, vulnerabilities_states): - # Get the indexer values - alerts_vulnerabilities = {} - indices_vulnerabilities = {} - - for agent, vuln_alerts in vulnerabilities_alerts.items(): - for vuln_alert in vuln_alerts: - if agent != vuln_alert['_source']['agent']['name']: - logging.critical("Agent name is not the same as the agent in the alert") - alert_agent = vuln_alert['_source']['agent']['name'] - alert_cve = vuln_alert['_source']['data']['vulnerability']['cve'] - alert_package_version = vuln_alert['_source']['data']['vulnerability']['package']['version'] - alert_package_name = vuln_alert['_source']['data']['vulnerability']['package']['name'] - - if agent not in alerts_vulnerabilities: - alerts_vulnerabilities[agent] = [] - - alerts_vulnerabilities[agent].append({ - 'cve': alert_cve, - 'agent': alert_agent, - 'package_name': alert_package_name, - 'package_version': alert_package_version - }) - - for agent, vulnerabilities in vulnerabilities_states.items(): - for vulnerability in vulnerabilities: - if agent != vulnerability['_source']['agent']['name']: - logging.critical("Agent name is not the same as the agent in the vulnerability state") - - state_agent = vulnerability['_source']['agent']['name'] - state_cve = vulnerability['_source']['vulnerability']['id'] - state_package_name = vulnerability['_source']['package']['name'] - state_package_version = vulnerability['_source']['package']['version'] - - if agent not in indices_vulnerabilities: - indices_vulnerabilities[agent] = [] - - indices_vulnerabilities[agent].append({ - 'cve': state_cve, - 'agent': state_agent, - 'package_name': state_package_name, - 'package_version': state_package_version - }) - - if vulnerabilities_states.keys() != vulnerabilities_alerts.keys(): - logging.critical("The number of agents is not the same between alerts and states") - - agents_in_alerts_states = [agent for agent in vulnerabilities_states.keys() - if agent in vulnerabilities_alerts.keys()] - - alerts_not_in_states = [] - states_not_in_alerts = [] - - for agent in agents_in_alerts_states: - agent_alerts = [] if agent not in alerts_vulnerabilities else alerts_vulnerabilities[agent] - agent_states = [] if agent not in indices_vulnerabilities else indices_vulnerabilities[agent] - - if len(agent_alerts) != len(agent_states): - logging.critical(f"The number of alerts is not the same as the number of states for agent {agent}") - logging.critical(f"Alerts: {len(agent_alerts)}") - logging.critical(f"States: {len(agent_states)}") - - # Check that all alerts are in the index - for alert in agent_alerts: - if alert not in agent_states: - alerts_not_in_states.append(alert) - - # Check that all index states are in the alerts - for state in agent_states: - if state not in agent_alerts: - alerts_not_in_states.append(state) - - return { - 'alerts_not_in_states': alerts_not_in_states, - 'states_not_in_alerts': states_not_in_alerts - } - - -def get_vulnerabilities_from_states(vulnerabilities_states: List) -> List: +def parse_vulnerabilities_from_states(vulnerabilities_states: List) -> List: """Parse vulnerabilities from the vulnerability state index. Args: vulnerabilities_states (list): List of vulnerabilities from the vulnerability state index. + Example of vulnerabilities_states: + [ + { + "_index":"wazuh-vulnerabilities-4.x-2024.04.22", + "_id":"YqpJBY8BJodbzcVedMR2", + "_score":4.6972857, + ... + "data":{ + "vulnerability":{ + "severity":"Medium", + "package":{ + "condition":"Package less than 0.7.0", + "name":"http-proxy", + "source":" ", + "version":"0.5.9", + "architecture":" " + }, + ... + } + } + } + ] + Returns: list: List of vulnerabilities sorted by cve, package_name, package_version, and architecture. + + Example of return value: + [ + Vulnerability(cve='CVE-2021-25804', package_name='http-proxy', package_version='0.5.9', architecture=''), + ] """ - vulnerabilities = [] + def parse_vulnerability_from_state(state): + source_data = state.get('_source', {}) + package_data = source_data.get('package', {}) + vulnerability_data = source_data.get('vulnerability', {}) + architecture = normalize_architecture(package_data.get('architecture', '')) - for state_vulnerability in vulnerabilities_states: + vulnerability = Vulnerability( + cve=vulnerability_data.get('id', ''), + package_name=package_data.get('name', ''), + package_version=package_data.get('version', ''), + architecture=architecture + ) - architecture = state_vulnerability['_source']['package']['architecture'] \ - if 'package' in state_vulnerability['_source'] and \ - 'architecture' in state_vulnerability['_source']['package'] else "" + return vulnerability - if architecture == 'amd64': - architecture = 'x86_64' - elif architecture == 'aarch64': - architecture = 'arm64' + vulnerabilities = [] + for state_vulnerability in vulnerabilities_states: try: - vulnerability = Vulnerability( - cve=state_vulnerability['_source']['vulnerability']['id'], - package_name=(state_vulnerability['_source']['package']['name'] - if 'package' in state_vulnerability['_source'] - and 'name' in state_vulnerability['_source']['package'] else ""), - package_version=(state_vulnerability['_source']['package']['version'] - if 'package' in state_vulnerability['_source'] - and 'version' in state_vulnerability['_source']['package'] else ""), - architecture=architecture - ) + vulnerability = parse_vulnerability_from_state(state_vulnerability) vulnerabilities.append(vulnerability) except KeyError as e: - logging.error(f"Error parsing vulnerability: {state_vulnerability}") + logging.error(f"Error parsing vulnerability: {state_vulnerability}: {str(e)}") raise e vulnerabilities = sorted(vulnerabilities, key=lambda x: (x.cve, x.package_name, x.package_version, x.architecture)) @@ -381,16 +276,36 @@ def get_vulnerabilities_from_states(vulnerabilities_states: List) -> List: def get_vulnerabilities_from_states_by_agent(host_manager: HostManager, agents: List[str], greater_than_timestamp: str = None) -> dict: + """Get vulnerabilities from the vulnerability state index by agent. + + Args: + host_manager (HostManager): Host manager object. + agents (list): List of agents. + greater_than_timestamp (str, optional): Greater than timestamp. Defaults to None. + + Returns: + dict: Dictionary of vulnerabilities by agent. + + Example of return value: + { + 'agent1': [ + Vulnerability(cve='CVE-2021-25804', package_name='http-proxy', package_version='0.5.9', architecture=''), + ], + 'agent2': [ + Vulnerability(cve='CVE-2021-25804', package_name='http-proxy', package_version='0.5.9', architecture=''), + ], + } + """ vuln_by_agent_index = {} indexer_user, indexer_password = host_manager.get_indexer_credentials() for agent in agents: agent_all_vulnerabilities = [] try: - filter = create_vulnerability_states_indexer_filter(target_agent=agent, - greater_than_timestamp=greater_than_timestamp) + states_filter = create_vulnerability_states_indexer_filter(target_agent=agent, + greater_than_timestamp=greater_than_timestamp) agent_all_vulnerabilities = get_indexer_values(host_manager, - filter=filter, + filter=states_filter, index=WAZUH_STATES_VULNERABILITIES_INDEXNAME, credentials={'user': indexer_user, 'password': indexer_password} @@ -398,12 +313,32 @@ def get_vulnerabilities_from_states_by_agent(host_manager: HostManager, agents: except KeyError as e: logging.error(f"No vulnerabilities were obtained for {agent}. Exception {str(e)}") - vuln_by_agent_index[agent] = get_vulnerabilities_from_states(agent_all_vulnerabilities) + vuln_by_agent_index[agent] = parse_vulnerabilities_from_states(agent_all_vulnerabilities) return vuln_by_agent_index def get_vulnerabilities_from_alerts_by_agent(host_manager: HostManager, agents: List[str], greater_than_timestamp: str): + """Get vulnerabilities from the vulnerability alert index by agent. + + Args: + host_manager (HostManager): Host manager object. + agents (list): List of agents. + greater_than_timestamp (str, optional): Greater than timestamp. Defaults to None. + + Returns: + dict: Dictionary of vulnerabilities by agent. + + Example of return value: + { + 'agent1': [ + Vulnerability(cve='CVE-2021-25804', package_name='http-proxy', package_version='0.5.9', architecture=''), + ], + 'agent2': [ + Vulnerability(cve='CVE-2021-25804', package_name='http-proxy', package_version='0.5.9', architecture=''), + ], + } + """ vuln_by_agent_index = { 'mitigated': {}, 'affected': {} @@ -413,10 +348,10 @@ def get_vulnerabilities_from_alerts_by_agent(host_manager: HostManager, agents: for agent in agents: agent_all_vulnerabilities = [] try: - filter = create_alerts_filter(target_agent=agent, greater_than_timestamp=greater_than_timestamp) + alerts_filter = create_alerts_filter(target_agent=agent, greater_than_timestamp=greater_than_timestamp) vuln_by_agent_index['mitigated'][agent] = [] agent_all_alerts = get_indexer_values(host_manager, - filter=filter, + filter=alerts_filter, credentials={'user': indexer_user, 'password': indexer_password})['hits']['hits'] agent_all_vulnerabilities = get_vulnerability_detector_alerts(agent_all_alerts) From 4ae819f894add15c610dbdaf8a30cc73cae57491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rebollo=20P=C3=A9rez?= Date: Mon, 6 May 2024 09:52:38 +0100 Subject: [PATCH 2/2] docs: include 5337 changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f82cebb7c..91c4b7d572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ All notable changes to this project will be documented in this file. ### Fixed +- Fix macOS alert collection for E2E Vulnerability Detection tests ([#5337](https://github.com/wazuh/wazuh-qa/pull/5337)) \- (Framework) - Fix packages in Windows and macOS upgrade cases ([#5223](https://github.com/wazuh/wazuh-qa/pull/5223)) \- (Framework + Tests) - Fix vulnerabilities and add new packages to Vulnerability Detector E2E tests ([#5234](https://github.com/wazuh/wazuh-qa/pull/5234)) \- (Tests) - Fix provision macOS endpoints with npm ([#5128](https://github.com/wazuh/wazuh-qa/pull/5158)) \- (Tests)