diff --git a/vulnerabilities/importer.py b/vulnerabilities/importer.py index e1564934e..3c4fbfa2d 100644 --- a/vulnerabilities/importer.py +++ b/vulnerabilities/importer.py @@ -74,7 +74,6 @@ def from_dict(cls, severity: dict): @dataclasses.dataclass(order=True) class Reference: - reference_id: str = "" url: str = "" severities: List[VulnerabilitySeverity] = dataclasses.field(default_factory=list) @@ -437,48 +436,55 @@ def get_data_from_xml_doc( for definition_data in raw_data: # These fields are definition level, i.e common for all elements # connected/linked to an OvalDefinition - vuln_id = definition_data["vuln_id"] - description = definition_data["description"] - severities = [] - severity = definition_data.get("severity") - if severity: - severities.append( - VulnerabilitySeverity(system=severity_systems.GENERIC, value=severity) - ) - references = [ - Reference(url=url, severities=severities) - for url in definition_data["reference_urls"] - ] - affected_packages = [] - for test_data in definition_data["test_data"]: - for package_name in test_data["package_list"]: - affected_version_range = test_data["version_ranges"] - vrc = RANGE_CLASS_BY_SCHEMES[pkg_metadata["type"]] - if affected_version_range: - try: - affected_version_range = vrc.from_native(affected_version_range) - except Exception as e: - logger.error( - f"Failed to parse version range {affected_version_range!r} " - f"for package {package_name!r}:\n{e}\n" - f"{definition_data!r}" - ) - continue - if package_name: - affected_packages.append( - AffectedPackage( - package=self.create_purl(package_name, pkg_metadata), - affected_version_range=affected_version_range, + + # NOTE: This is where we loop through the list of CVEs/aliases. + vuln_id_list = definition_data["vuln_id"] + + for vuln_id_item in vuln_id_list: + vuln_id = vuln_id_item + description = definition_data["description"] + + severities = [] + severity = definition_data.get("severity") + if severity: + severities.append( + VulnerabilitySeverity(system=severity_systems.GENERIC, value=severity) + ) + references = [ + Reference(url=url, severities=severities) + for url in definition_data["reference_urls"] + ] + affected_packages = [] + + for test_data in definition_data["test_data"]: + for package_name in test_data["package_list"]: + affected_version_range = test_data["version_ranges"] + vrc = RANGE_CLASS_BY_SCHEMES[pkg_metadata["type"]] + if affected_version_range: + try: + affected_version_range = vrc.from_native(affected_version_range) + except Exception as e: + logger.error( + f"Failed to parse version range {affected_version_range!r} " + f"for package {package_name!r}:\n{e}" + ) + continue + if package_name: + affected_packages.append( + AffectedPackage( + package=self.create_purl(package_name, pkg_metadata), + affected_version_range=affected_version_range, + ) ) - ) - date_published = dateparser.parse(timestamp) - if not date_published.tzinfo: - date_published = date_published.replace(tzinfo=pytz.UTC) - yield AdvisoryData( - aliases=[vuln_id], - summary=description, - affected_packages=affected_packages, - references=sorted(references), - date_published=date_published, - url=self.data_url, - ) + + date_published = dateparser.parse(timestamp) + if not date_published.tzinfo: + date_published = date_published.replace(tzinfo=pytz.UTC) + yield AdvisoryData( + aliases=[vuln_id], + summary=description, + affected_packages=sorted(affected_packages), + references=sorted(references), + date_published=date_published, + url=self.data_url, + ) diff --git a/vulnerabilities/importers/suse_oval.py b/vulnerabilities/importers/suse_oval.py new file mode 100644 index 000000000..5ac35e23e --- /dev/null +++ b/vulnerabilities/importers/suse_oval.py @@ -0,0 +1,69 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# VulnerableCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + + +import gzip +import xml.etree.ElementTree as ET + +import requests +from bs4 import BeautifulSoup + +from vulnerabilities.importer import OvalImporter + + +class SuseOvalImporter(OvalImporter): + spdx_license_expression = "CC-BY-4.0" + license_url = "https://ftp.suse.com/pub/projects/security/oval/LICENSE" + base_url = "https://ftp.suse.com/pub/projects/security/oval/" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.translations = {"less than": "<", "equals": "=", "greater than or equal": ">="} + + def _fetch(self): + page = requests.get(self.base_url).text + soup = BeautifulSoup(page, "lxml") + + suse_oval_files = [ + self.base_url + node.get("href") + for node in soup.find_all("a") + if node.get("href").endswith(".gz") + ] + + for suse_file in filter(suse_oval_files): + response = requests.get(suse_file) + + extracted = gzip.decompress(response.content) + yield ( + {"type": "rpm", "namespace": "opensuse"}, + ET.ElementTree(ET.fromstring(extracted.decode("utf-8"))), + ) + + +def filter(suse_oval_files): + """ + Filter to exclude "name.xml" when we also have "name-affected.xml", e.g., + "opensuse.leap.15.3.xml.gz" vs. "opensuse.leap.15.3-affected.xml.gz". See + https://ftp.suse.com/pub/projects/security/oval/README: "name-affected.xml" includes + "fixed security issues and the analyzed issues both affecting and NOT affecting SUSE" and + "name.xml" includes "fixed security issues and the analyzed issues NOT affecting SUSE." + """ + affected_files = [ + affected_file for affected_file in suse_oval_files if "-affected" in affected_file + ] + + trimmed_affected_files = [ + affected_file.replace("-affected", "") for affected_file in affected_files + ] + + filtered_suse_oval_files = [ + gz_file for gz_file in suse_oval_files if gz_file not in trimmed_affected_files + ] + + return filtered_suse_oval_files diff --git a/vulnerabilities/lib_oval.py b/vulnerabilities/lib_oval.py index 6c8a6163f..0b178d47c 100644 --- a/vulnerabilities/lib_oval.py +++ b/vulnerabilities/lib_oval.py @@ -62,8 +62,8 @@ Available exceptions: - None at this time - - + + :Usage: 1. Create an OvalDocument: @@ -80,21 +80,21 @@ 3. Read an XML file with a single OVAL Definition (error checking omitted for brevity): - >>> tree = ElementTree() + >>> tree = ElementTree() >>> tree.parse('test-definition.xml') - >>> root = tree.getroot() + >>> root = tree.getroot() >>> definition = lib_oval.OvalDefinition(root) - + 4. Change information in the definition from #3 and write the changes >>> meta = definition.getMetadata() >>> repo = meta.getOvalRepositoryInformation() >>> repo.setMinimumSchemaVersion("5.9") >>> tree.write("outfilename.xml", UTF-8", True) - - + + TODO: - Add exceptions that give more detail about why a value of None is sometimes returned @@ -253,7 +253,6 @@ def writeToFile(self, filename): return False def to_string(self): - if not self.tree: return None @@ -767,6 +766,12 @@ def setVersion(self, version): self.element.set("version", version) return True + def __lt__(self, other): + try: + return int(self.element.get("version")) < int(other.element.get("version")) + except: + return NotImplemented + def incrementVersion(self): version = self.getVersion() if not version: diff --git a/vulnerabilities/oval_parser.py b/vulnerabilities/oval_parser.py index 94b360bff..2a958312c 100755 --- a/vulnerabilities/oval_parser.py +++ b/vulnerabilities/oval_parser.py @@ -25,7 +25,6 @@ class OvalParser: def __init__(self, translations: Dict, oval_document: ET.ElementTree): - self.translations = translations self.oval_document = OvalDocument(oval_document) self.all_definitions = self.oval_document.getDefinitions() @@ -37,17 +36,14 @@ def get_data(self) -> List[Dict]: """ oval_data = [] for definition in self.all_definitions: - matching_tests = self.get_tests_of_definition(definition) if not matching_tests: continue definition_data = {"test_data": []} # TODO:this could use some data cleaning definition_data["description"] = definition.getMetadata().getDescription() or "" - definition_data["vuln_id"] = self.get_vuln_id_from_definition(definition) definition_data["reference_urls"] = self.get_urls_from_definition(definition) - definition_data["severity"] = self.get_severity_from_definition(definition) for test in matching_tests: @@ -72,24 +68,30 @@ def get_tests_of_definition(self, definition: OvalDefinition) -> List[OvalTest]: criteria_refs = [] for child in definition.element.iter(): - if "test_ref" in child.attrib: criteria_refs.append(child.get("test_ref")) matching_tests = [] for ref in criteria_refs: oval_test = self.oval_document.getElementByID(ref) + # All matches will be `rpminfo_test` elements inside the `tests` element. + # Test for len == 2 because this IDs a pair of nested `object` and `state` elements. if len(oval_test.element) == 2: _, state = self.get_object_state_of_test(oval_test) valid_test = True for child in state.element: if child.get("operation") not in self.translations: valid_test = False - break - if valid_test: - matching_tests.append(self.oval_document.getElementByID(ref)) + continue + elif ( + child.get("operation") in self.translations + # "debian_evr_string" is used in both Debian and Ubuntu test XML files; SUSE OVAL uses "evr_string". + # See also https://github.com/OVALProject/Language/blob/master/docs/oval-common-schema.md + and child.get("datatype") in ["evr_string", "debian_evr_string"] + ): + matching_tests.append(self.oval_document.getElementByID(ref)) - return matching_tests + return sorted(set(matching_tests)) def get_object_state_of_test(self, test: OvalTest) -> Tuple[OvalObject, OvalState]: """ @@ -109,6 +111,7 @@ def get_pkgs_from_obj(self, obj: OvalObject) -> List[str]: pkg_list = [] for var in obj.element: + # It appears that `var_ref` is used in Ubuntu OVAL but not Debian or SUSE. if var.get("var_ref"): var_elem = self.oval_document.getElementByID(var.get("var_ref")) comment = var_elem.element.get("comment") @@ -178,9 +181,18 @@ def get_severity_from_definition(definition: OvalDefinition) -> Set[str]: @staticmethod def get_vuln_id_from_definition(definition): - # SUSE and Ubuntu OVAL files will get cves via this loop + # SUSE and Ubuntu OVAL files will get CVEs via this loop. + cve_list = [] for child in definition.element.iter(): - if child.get("ref_id"): - return child.get("ref_id") - # Debian OVAL files will get cves via this - return definition.getMetadata().getTitle() + if child.get("ref_id") and child.get("source"): + if child.get("source") == "CVE": + if not child.get("ref_id").startswith("CVE"): + unwanted_prefix = child.get("ref_id").split("CVE")[0] + cve_list.append(child.get("ref_id").replace(unwanted_prefix, "")) + else: + cve_list.append(child.get("ref_id")) + # Debian OVAL files (no "ref_id") will get CVEs via this. + if len(cve_list) == 0: + cve_list.append(definition.getMetadata().getTitle()) + + return cve_list diff --git a/vulnerabilities/tests/test_data/suse_oval/mock-definitions-only.xml b/vulnerabilities/tests/test_data/suse_oval/mock-definitions-only.xml new file mode 100644 index 000000000..8d7c7756e --- /dev/null +++ b/vulnerabilities/tests/test_data/suse_oval/mock-definitions-only.xml @@ -0,0 +1,42 @@ + + + + + + CVE-2008-5679 + + + + + The HTML parsing engine in Opera before 9.63 allows remote attackers to execute arbitrary code via crafted web pages that trigger an invalid pointer calculation and heap corruption. + + + + + + foobar-CVE-1234-5678 + + + + + Blah blah blah. + + + + + + nonesuchCVE-1111-2222 + + + + + Blah blah blah. + + + + + diff --git a/vulnerabilities/tests/test_data/suse_oval/org.opensuse.CVE-2008-5679-modified-versions.xml b/vulnerabilities/tests/test_data/suse_oval/org.opensuse.CVE-2008-5679-modified-versions.xml new file mode 100644 index 000000000..9ad88ee30 --- /dev/null +++ b/vulnerabilities/tests/test_data/suse_oval/org.opensuse.CVE-2008-5679-modified-versions.xml @@ -0,0 +1,90 @@ + + + + Marcus OVAL Generator + 5.5 + 2009-01-14T09:08:29.480-05:00 + + + + + + CVE-2008-5679 + + + + + The HTML parsing engine in Opera before 9.63 allows remote attackers to execute arbitrary code via crafted web pages that trigger an invalid pointer calculation and heap corruption. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + opera + + + openSUSE-release + + + + + 0:9.63-1.1 + + + ^10.3$ + + + ^11.0$ + + + ^11.1$ + + + diff --git a/vulnerabilities/tests/test_data/suse_oval/org.opensuse.CVE-2008-5679.xml b/vulnerabilities/tests/test_data/suse_oval/org.opensuse.CVE-2008-5679.xml new file mode 100644 index 000000000..b7c4c5e73 --- /dev/null +++ b/vulnerabilities/tests/test_data/suse_oval/org.opensuse.CVE-2008-5679.xml @@ -0,0 +1,81 @@ + + + + Marcus OVAL Generator + 5.5 + 2009-01-14T09:08:29.480-05:00 + + + + + + CVE-2008-5679 + + + + + The HTML parsing engine in Opera before 9.63 allows remote attackers to execute arbitrary code via crafted web pages that trigger an invalid pointer calculation and heap corruption. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + opera + + + openSUSE-release + + + + + 0:9.63-1.1 + + + ^10.3$ + + + ^11.0$ + + + ^11.1$ + + + diff --git a/vulnerabilities/tests/test_data/suse_oval/suse-oval-CVE-2008-5679-expected.json b/vulnerabilities/tests/test_data/suse_oval/suse-oval-CVE-2008-5679-expected.json new file mode 100644 index 000000000..93469b4a0 --- /dev/null +++ b/vulnerabilities/tests/test_data/suse_oval/suse-oval-CVE-2008-5679-expected.json @@ -0,0 +1,32 @@ +[ + { + "aliases": [ + "CVE-2008-5679" + ], + "summary": "The HTML parsing engine in Opera before 9.63 allows remote attackers to execute arbitrary code via crafted web pages that trigger an invalid pointer calculation and heap corruption.", + "affected_packages": [ + { + "package": { + "type": "rpm", + "namespace": "opensuse", + "name": "opera", + "version": null, + "qualifiers": null, + "subpath": null + }, + "affected_version_range": "vers:rpm/<9.63-1.1", + "fixed_version": null + } + ], + "references": [ + { + "reference_id": "", + "url": "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-5679", + "severities": [] + } + ], + "date_published": "2009-01-14T09:08:29.480000-05:00", + "weaknesses": [], + "url": "" + } +] diff --git a/vulnerabilities/tests/test_data_source.py b/vulnerabilities/tests/test_data_source.py index e1a36c57d..369ac9c23 100644 --- a/vulnerabilities/tests/test_data_source.py +++ b/vulnerabilities/tests/test_data_source.py @@ -31,6 +31,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) TEST_DATA = os.path.join(BASE_DIR, "test_data/") +TEST_DATA_01 = os.path.join(BASE_DIR, "test_data/suse_oval") def load_oval_data(): @@ -76,7 +77,6 @@ def test_create_purl(): def test__collect_pkgs(): - xmls = load_oval_data() expected_suse_pkgs = {"cacti-spine", "apache2-mod_perl", "cacti", "apache2-mod_perl-devel"} @@ -138,3 +138,28 @@ def test_git_importer_clone(git_importer): list(git_importer().advisory_data()) mock_fetch.assert_called_once() mock_delete.assert_called_once() + + +# Here we use a modified copy of org.opensuse.CVE-2008-5679.xml -- the test versions are modified to illustrate sort order. +def test_ovaltest_sorting(): + xml_doc = ET.parse( + os.path.join(TEST_DATA_01, "org.opensuse.CVE-2008-5679-modified-versions.xml") + ) + translations = {"less than": "<", "equals": "=", "greater than or equal": ">="} + parsed_oval = OvalParser(translations, xml_doc) + + # Get the list of all tests and check the total number of tests. + get_all_tests = parsed_oval.oval_document.getTests() + + # Check the order of the four tests in the sorted `get_all_tests` list. (Testing suggests that the + # original list of tests, `get_all_tests`, is unsorted and is ordered in the same order as the test + # elements appear in the .xml file.) + sorted_tests = sorted(get_all_tests) + test_results = [(test.getId(), test.getVersion()) for test in sorted_tests] + expected = [ + ("oval:org.opensuse.security:tst:2009030401", "1"), + ("oval:org.opensuse.security:tst:2009030403", "4"), + ("oval:org.opensuse.security:tst:2009030402", "9"), + ("oval:org.opensuse.security:tst:2009030400", "11"), + ] + assert test_results == expected diff --git a/vulnerabilities/tests/test_suse_oval.py b/vulnerabilities/tests/test_suse_oval.py new file mode 100644 index 000000000..eba1ca0f2 --- /dev/null +++ b/vulnerabilities/tests/test_suse_oval.py @@ -0,0 +1,157 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# VulnerableCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + + +import gzip +import io +import os +import xml.etree.ElementTree as ET + +from vulnerabilities.importers.suse_oval import SuseOvalImporter +from vulnerabilities.importers.suse_oval import filter +from vulnerabilities.oval_parser import OvalParser +from vulnerabilities.tests import util_tests + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +TEST_DATA = os.path.join(BASE_DIR, "test_data/suse_oval") + + +def test_suse_oval_importer_CVE_2008_5679(): + importer = SuseOvalImporter() + advisories = importer.get_data_from_xml_doc( + ET.parse(os.path.join(TEST_DATA, "org.opensuse.CVE-2008-5679.xml")), + {"type": "rpm", "namespace": "opensuse"}, + ) + expected_file = os.path.join(TEST_DATA, f"suse-oval-CVE-2008-5679-expected.json") + util_tests.check_results_against_json( + [advisory.to_dict() for advisory in advisories], expected_file + ) + + +def test_suse_oval_parse_CVE_2008_5679(): + xml_doc = ET.parse(os.path.join(TEST_DATA, "org.opensuse.CVE-2008-5679.xml")) + translations = {"less than": "<", "equals": "=", "greater than or equal": ">="} + parsed_oval = OvalParser(translations, xml_doc) + + # Get total number of definitions + assert len(parsed_oval.all_definitions) == 1 + + # Get definition `id`: the `` element. + definition_1 = parsed_oval.all_definitions[0] + assert parsed_oval.all_definitions[0].getId() == "oval:org.opensuse.security:def:2009030400" + + # Get definition `test_ref`: the `` element. + definition_1_test_ids = { + "oval:org.opensuse.security:tst:2009030400", + } + assert definition_1_test_ids == { + i.getId() for i in parsed_oval.get_tests_of_definition(definition_1) + } + + # Get vuln_id from definition + vuln_id_1 = ["CVE-2008-5679"] + assert vuln_id_1 == parsed_oval.get_vuln_id_from_definition(definition_1) + + # Get total number of tests + assert len(parsed_oval.oval_document.getTests()) == 4 + + # Get test object and test state + test_1 = parsed_oval.oval_document.getTests()[0] + obj_t1, state_t1 = parsed_oval.get_object_state_of_test(test_1) + assert obj_t1.getId() == "oval:org.opensuse.security:obj:2009030400" + assert state_t1.getId() == "oval:org.opensuse.security:ste:2009030400" + + # Get total number of packages: `rpminfo_object` elements + assert len(parsed_oval.oval_document.getObjects()) == 2 + + # Get packages + obj_t1 = parsed_oval.oval_document.getObjects()[0] + obj_t2 = parsed_oval.oval_document.getObjects()[1] + + pkg_set1 = set(parsed_oval.get_pkgs_from_obj(obj_t1)) + pkg_set2 = set(parsed_oval.get_pkgs_from_obj(obj_t2)) + + assert pkg_set1 == {"opera"} + assert pkg_set2 == {"openSUSE-release"} + + # Get total number of versions: `rpminfo_state` elements + assert len(parsed_oval.oval_document.getStates()) == 4 + + # Get versions + state_1 = parsed_oval.oval_document.getStates()[0] + + exp_range_1 = "<9.63-1.1" + + assert parsed_oval.get_version_range_from_state(state_1) == exp_range_1 + + # Get reference URLs: `ref_url` attribute from `reference` elements + definition_0 = parsed_oval.all_definitions[0] + def0_urls = { + "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-5679", + } + + assert def0_urls == parsed_oval.get_urls_from_definition(definition_0) + + +def test_filter_suse_gz_files(): + initial_suse_gz_files = [ + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.7-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.7-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.7.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.8-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.8-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.8.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.9-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.9-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.9.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.6-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.6-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.6.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.7-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.7-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.7.xml.gz", + ] + + filtered_initial_suse_gz_files = [ + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.7-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.7-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.8-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.8-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.9-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.openstack.cloud.9-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.6-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.6-patch.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.7-affected.xml.gz", + "https://ftp.suse.com/pub/projects/security/oval/suse.storage.7-patch.xml.gz", + ] + + assert filter(initial_suse_gz_files) == filtered_initial_suse_gz_files + + +def test_cve_prefix_filter(): + xml_doc = ET.parse(os.path.join(TEST_DATA, "mock-definitions-only.xml")) + translations = {"less than": "<", "equals": "=", "greater than or equal": ">="} + parsed_oval = OvalParser(translations, xml_doc) + + assert len(parsed_oval.all_definitions) == 3 + + definition_1 = parsed_oval.all_definitions[0] + + vuln_id_1 = ["CVE-2008-5679"] + assert vuln_id_1 == parsed_oval.get_vuln_id_from_definition(definition_1) + + definition_2 = parsed_oval.all_definitions[1] + + vuln_id_2 = ["CVE-1234-5678"] + assert vuln_id_2 == parsed_oval.get_vuln_id_from_definition(definition_2) + + definition_3 = parsed_oval.all_definitions[2] + + vuln_id_3 = ["CVE-1111-2222"] + assert vuln_id_3 == parsed_oval.get_vuln_id_from_definition(definition_3)