From 208a9d563ee62dc92cd9b4d57e89c2bf8e869711 Mon Sep 17 00:00:00 2001 From: NucleonGodX Date: Wed, 15 Jan 2025 11:53:56 +0530 Subject: [PATCH 1/5] initial push, add apachelog4importer Signed-off-by: NucleonGodX --- vulnerabilities/importers/__init__.py | 2 + vulnerabilities/importers/apache_log4j.py | 183 ++++++++++++++++++++ vulnerabilities/improvers/__init__.py | 1 + vulnerabilities/improvers/valid_versions.py | 6 + 4 files changed, 192 insertions(+) create mode 100644 vulnerabilities/importers/apache_log4j.py diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 3f429f669..1dd887d33 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -9,6 +9,7 @@ from vulnerabilities.importers import apache_httpd from vulnerabilities.importers import apache_kafka +from vulnerabilities.importers import apache_log4j from vulnerabilities.importers import apache_tomcat from vulnerabilities.importers import archlinux from vulnerabilities.importers import curl @@ -60,6 +61,7 @@ suse_scores.SUSESeverityScoreImporter, elixir_security.ElixirSecurityImporter, apache_tomcat.ApacheTomcatImporter, + apache_log4j.ApacheLog4jImporter, xen.XenImporter, ubuntu_usn.UbuntuUSNImporter, fireeye.FireyeImporter, diff --git a/vulnerabilities/importers/apache_log4j.py b/vulnerabilities/importers/apache_log4j.py new file mode 100644 index 000000000..7c5c2917d --- /dev/null +++ b/vulnerabilities/importers/apache_log4j.py @@ -0,0 +1,183 @@ +import logging +import requests +import xml.etree.ElementTree as ET +import pytz +from dateutil.parser import parse +from packageurl import PackageURL +from vulnerabilities.importer import AdvisoryData, AffectedPackage, Importer, Reference +from univers.version_range import MavenVersionRange + +logger = logging.getLogger(__name__) + +class ApacheLog4jImporter(Importer): + XML_URL = "https://logging.apache.org/cyclonedx/vdr.xml" + ASF_PAGE_URL = "https://logging.apache.org/security.html" + spdx_license_expression = "Apache-2.0" + license_url = "https://www.apache.org/licenses/" + importer_name = "Apache Log4j Importer" + + @staticmethod + def fetch_advisory_page(): + """ + Fetch the Log4j vulnerability XML feed. + """ + try: + response = requests.get(ApacheLog4jImporter.XML_URL, timeout=30) + response.raise_for_status() + return response.content + except requests.RequestException as e: + logger.error(f"Failed to fetch Log4j vulnerability data: {e}") + return None + + @staticmethod + def parse_version_range(range_str): + """ + Parse version range string and return start and end versions. + Example: "vers:maven/>=2.0-beta7|<2.3.2" -> ("2.0-beta7", "2.3.2") + """ + try: + range_parts = range_str.replace("vers:maven/", "").split("|") + start_version = range_parts[0].replace(">=", "").strip() + end_version = range_parts[1].replace("<", "").strip() + return start_version, end_version + except Exception as e: + logger.error(f"Error parsing version range {range_str}: {e}") + return None, None + + @staticmethod + def get_versions_in_range(start_version, end_version, version_mapping): + """ + Get all versions between start_version and end_version from version_mapping. + """ + try: + start_idx = version_mapping.index(start_version) + end_idx = version_mapping.index(end_version) + return version_mapping[start_idx:end_idx] + except ValueError as e: + logger.error(f"Error getting versions in range: {e}") + return [] + + def to_advisory(self, xml_content): + """ + Parse the XML content and create AdvisoryData objects. + """ + advisories = [] + version_mapping = [ + '2.0-beta1', '2.0-beta2', '2.0-beta3', '2.0-beta5', '2.0-alpha1', '2.0-beta7', + '2.0-beta8', '2.0-beta9', '2.0-rc1', '2.0-beta4', '2.0-beta6', '2.0-rc2', + '2.0', '2.0.1', '2.0.2', '2.1', '2.2', '2.3', '2.3.1', '2.3.2', '2.4', '2.4.1', + '2.5', '2.6', '2.6.1', '2.6.2', '2.7', '2.8', '2.8.1', '2.8.2', '2.9.0', '2.9.1', + '2.10.0', '2.11.0', '2.11.1', '2.11.2', '2.12.0', '2.12.1', '2.12.2', '2.12.3', + '2.12.4', '2.13.0', '2.13.1', '2.13.2', '2.13.3', '2.14.0', '2.14.1', '2.15.0', + '2.15.1', '2.16.0', '2.17.0', '2.17.1' + ] + + try: + root = ET.fromstring(xml_content) + ns = {'ns0': 'http://cyclonedx.org/schema/bom/1.5'} + vulnerabilities = root.findall('.//ns0:vulnerability', ns) + + for vuln in vulnerabilities: + cve_id = vuln.find('./ns0:id', ns) + if cve_id is None or not cve_id.text: + continue + cve_id = cve_id.text.strip() + + description = vuln.find('./ns0:description', ns) + description_text = description.text.strip() if description is not None else "" + + published_date_text = vuln.find('./ns0:published', ns) + date_published = None + if published_date_text is not None and published_date_text.text: + try: + date_published = parse(published_date_text.text.strip()).replace(tzinfo=pytz.UTC) + except ValueError as e: + logger.error(f"Error parsing date {published_date_text.text}: {e}") + + references = [ + Reference(url=f"https://nvd.nist.gov/vuln/detail/{cve_id}", reference_id=cve_id), + Reference(url=f"{self.ASF_PAGE_URL}#{cve_id}", reference_id=cve_id), + ] + + affected_packages = self.get_affected_packages(vuln, version_mapping) + + advisories.append( + AdvisoryData( + aliases=[cve_id], + summary=description_text, + affected_packages=affected_packages, + references=references, + date_published=date_published, + url=f"{self.ASF_PAGE_URL}#{cve_id}", + ) + ) + except ET.ParseError as e: + logger.error(f"Failed to parse XML content: {e}") + + return advisories + + def get_affected_packages(self, vuln, version_mapping): + """ + Extract affected packages from the vulnerability element. + """ + ns = {'ns0': 'http://cyclonedx.org/schema/bom/1.5'} + affected_packages = [] + + recommendation = vuln.find('.//ns0:recommendation', ns) + fixed_versions = [] + if recommendation is not None and recommendation.text: + fixed_versions = [v.strip('`') for v in recommendation.text.split('`') + if v.strip().replace('.', '').replace('-', '').isalnum()] + + targets = vuln.findall('.//ns0:target', ns) + for target in targets: + ref = target.find('./ns0:ref', ns) + if ref is None or not ref.text: + continue + + version_ranges = target.findall('./ns0:versions/ns0:version/ns0:range', ns) + for version_range in version_ranges: + if version_range is None or not version_range.text: + continue + + start_version, end_version = self.parse_version_range(version_range.text.strip()) + if start_version and end_version: + affected_versions = self.get_versions_in_range(start_version, end_version, version_mapping) + + fixed_version = self.get_fixed_version(fixed_versions, end_version, version_mapping) + + if fixed_version and affected_versions: + for affected_version in affected_versions: + affected_package = AffectedPackage( + package=PackageURL( + type="apache", + name="log4j-core", + ), + affected_version_range=MavenVersionRange.from_string(f"vers:maven/{affected_version}"), + fixed_version=fixed_version + ) + affected_packages.append(affected_package) + + return affected_packages + + @staticmethod + def get_fixed_version(fixed_versions, end_version, version_mapping): + """ + Get the fixed version from the list of fixed versions. + """ + for fix_ver in fixed_versions: + if fix_ver in version_mapping and version_mapping.index(fix_ver) >= version_mapping.index(end_version): + return fix_ver + return None + + def advisory_data(self): + """ + Fetch advisory data and convert it into AdvisoryData objects. + """ + xml_content = self.fetch_advisory_page() + if not xml_content: + logger.error("No XML content fetched.") + return [] + + advisories = self.to_advisory(xml_content) + return advisories \ No newline at end of file diff --git a/vulnerabilities/improvers/__init__.py b/vulnerabilities/improvers/__init__.py index 9b11c7920..0e06865e0 100644 --- a/vulnerabilities/improvers/__init__.py +++ b/vulnerabilities/improvers/__init__.py @@ -28,6 +28,7 @@ valid_versions.NpmImprover, valid_versions.ElixirImprover, valid_versions.ApacheTomcatImprover, + valid_versions.Log4jImprover, valid_versions.ApacheKafkaImprover, valid_versions.IstioImprover, valid_versions.DebianOvalImprover, diff --git a/vulnerabilities/improvers/valid_versions.py b/vulnerabilities/improvers/valid_versions.py index 916f36f59..ef7226e95 100644 --- a/vulnerabilities/improvers/valid_versions.py +++ b/vulnerabilities/improvers/valid_versions.py @@ -27,6 +27,7 @@ from vulnerabilities.importers.apache_httpd import ApacheHTTPDImporter from vulnerabilities.importers.apache_kafka import ApacheKafkaImporter from vulnerabilities.importers.apache_tomcat import ApacheTomcatImporter +from vulnerabilities.importers.apache_log4j import ApacheLog4jImporter from vulnerabilities.importers.curl import CurlImporter from vulnerabilities.importers.debian import DebianImporter from vulnerabilities.importers.debian_oval import DebianOvalImporter @@ -481,3 +482,8 @@ class GithubOSVImprover(ValidVersionImprover): class CurlImprover(ValidVersionImprover): importer = CurlImporter ignorable_versions = [] + + +class Log4jImprover(ValidVersionImprover): + importer = ApacheLog4jImporter + ignorable_versions = [] \ No newline at end of file From 434f63756129b6a80d91b14f3cad154bac0d88ed Mon Sep 17 00:00:00 2001 From: NucleonGodX Date: Wed, 15 Jan 2025 12:17:34 +0530 Subject: [PATCH 2/5] codestyle changes Signed-off-by: NucleonGodX --- vulnerabilities/importers/apache_log4j.py | 153 ++++++++++++++------ vulnerabilities/improvers/valid_versions.py | 4 +- 2 files changed, 113 insertions(+), 44 deletions(-) diff --git a/vulnerabilities/importers/apache_log4j.py b/vulnerabilities/importers/apache_log4j.py index 7c5c2917d..968b6ad3d 100644 --- a/vulnerabilities/importers/apache_log4j.py +++ b/vulnerabilities/importers/apache_log4j.py @@ -1,14 +1,20 @@ import logging -import requests import xml.etree.ElementTree as ET + import pytz +import requests from dateutil.parser import parse from packageurl import PackageURL -from vulnerabilities.importer import AdvisoryData, AffectedPackage, Importer, Reference from univers.version_range import MavenVersionRange +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import AffectedPackage +from vulnerabilities.importer import Importer +from vulnerabilities.importer import Reference + logger = logging.getLogger(__name__) + class ApacheLog4jImporter(Importer): XML_URL = "https://logging.apache.org/cyclonedx/vdr.xml" ASF_PAGE_URL = "https://logging.apache.org/security.html" @@ -63,39 +69,88 @@ def to_advisory(self, xml_content): """ advisories = [] version_mapping = [ - '2.0-beta1', '2.0-beta2', '2.0-beta3', '2.0-beta5', '2.0-alpha1', '2.0-beta7', - '2.0-beta8', '2.0-beta9', '2.0-rc1', '2.0-beta4', '2.0-beta6', '2.0-rc2', - '2.0', '2.0.1', '2.0.2', '2.1', '2.2', '2.3', '2.3.1', '2.3.2', '2.4', '2.4.1', - '2.5', '2.6', '2.6.1', '2.6.2', '2.7', '2.8', '2.8.1', '2.8.2', '2.9.0', '2.9.1', - '2.10.0', '2.11.0', '2.11.1', '2.11.2', '2.12.0', '2.12.1', '2.12.2', '2.12.3', - '2.12.4', '2.13.0', '2.13.1', '2.13.2', '2.13.3', '2.14.0', '2.14.1', '2.15.0', - '2.15.1', '2.16.0', '2.17.0', '2.17.1' + "2.0-beta1", + "2.0-beta2", + "2.0-beta3", + "2.0-beta5", + "2.0-alpha1", + "2.0-beta7", + "2.0-beta8", + "2.0-beta9", + "2.0-rc1", + "2.0-beta4", + "2.0-beta6", + "2.0-rc2", + "2.0", + "2.0.1", + "2.0.2", + "2.1", + "2.2", + "2.3", + "2.3.1", + "2.3.2", + "2.4", + "2.4.1", + "2.5", + "2.6", + "2.6.1", + "2.6.2", + "2.7", + "2.8", + "2.8.1", + "2.8.2", + "2.9.0", + "2.9.1", + "2.10.0", + "2.11.0", + "2.11.1", + "2.11.2", + "2.12.0", + "2.12.1", + "2.12.2", + "2.12.3", + "2.12.4", + "2.13.0", + "2.13.1", + "2.13.2", + "2.13.3", + "2.14.0", + "2.14.1", + "2.15.0", + "2.15.1", + "2.16.0", + "2.17.0", + "2.17.1", ] try: root = ET.fromstring(xml_content) - ns = {'ns0': 'http://cyclonedx.org/schema/bom/1.5'} - vulnerabilities = root.findall('.//ns0:vulnerability', ns) + ns = {"ns0": "http://cyclonedx.org/schema/bom/1.5"} + vulnerabilities = root.findall(".//ns0:vulnerability", ns) for vuln in vulnerabilities: - cve_id = vuln.find('./ns0:id', ns) + cve_id = vuln.find("./ns0:id", ns) if cve_id is None or not cve_id.text: continue cve_id = cve_id.text.strip() - description = vuln.find('./ns0:description', ns) + description = vuln.find("./ns0:description", ns) description_text = description.text.strip() if description is not None else "" - published_date_text = vuln.find('./ns0:published', ns) + published_date_text = vuln.find("./ns0:published", ns) date_published = None if published_date_text is not None and published_date_text.text: try: - date_published = parse(published_date_text.text.strip()).replace(tzinfo=pytz.UTC) + date_published = parse(published_date_text.text.strip()).replace( + tzinfo=pytz.UTC + ) except ValueError as e: logger.error(f"Error parsing date {published_date_text.text}: {e}") references = [ - Reference(url=f"https://nvd.nist.gov/vuln/detail/{cve_id}", reference_id=cve_id), + Reference( + url=f"https://nvd.nist.gov/vuln/detail/{cve_id}", reference_id=cve_id + ), Reference(url=f"{self.ASF_PAGE_URL}#{cve_id}", reference_id=cve_id), ] @@ -120,43 +175,55 @@ def get_affected_packages(self, vuln, version_mapping): """ Extract affected packages from the vulnerability element. """ - ns = {'ns0': 'http://cyclonedx.org/schema/bom/1.5'} + ns = {"ns0": "http://cyclonedx.org/schema/bom/1.5"} affected_packages = [] - recommendation = vuln.find('.//ns0:recommendation', ns) + recommendation = vuln.find(".//ns0:recommendation", ns) fixed_versions = [] if recommendation is not None and recommendation.text: - fixed_versions = [v.strip('`') for v in recommendation.text.split('`') - if v.strip().replace('.', '').replace('-', '').isalnum()] + fixed_versions = [ + v.strip("`") + for v in recommendation.text.split("`") + if v.strip().replace(".", "").replace("-", "").isalnum() + ] - targets = vuln.findall('.//ns0:target', ns) + targets = vuln.findall(".//ns0:target", ns) for target in targets: - ref = target.find('./ns0:ref', ns) + ref = target.find("./ns0:ref", ns) if ref is None or not ref.text: continue - version_ranges = target.findall('./ns0:versions/ns0:version/ns0:range', ns) + version_ranges = target.findall("./ns0:versions/ns0:version/ns0:range", ns) for version_range in version_ranges: if version_range is None or not version_range.text: continue start_version, end_version = self.parse_version_range(version_range.text.strip()) - if start_version and end_version: - affected_versions = self.get_versions_in_range(start_version, end_version, version_mapping) - - fixed_version = self.get_fixed_version(fixed_versions, end_version, version_mapping) - - if fixed_version and affected_versions: - for affected_version in affected_versions: - affected_package = AffectedPackage( - package=PackageURL( - type="apache", - name="log4j-core", - ), - affected_version_range=MavenVersionRange.from_string(f"vers:maven/{affected_version}"), - fixed_version=fixed_version - ) - affected_packages.append(affected_package) + if not start_version or not end_version: + continue + + affected_versions = self.get_versions_in_range( + start_version, end_version, version_mapping + ) + if not affected_versions: + continue + + fixed_version = self.get_fixed_version(fixed_versions, end_version, version_mapping) + if not fixed_version: + continue + + for affected_version in affected_versions: + affected_package = AffectedPackage( + package=PackageURL( + type="apache", + name="log4j-core", + ), + affected_version_range=MavenVersionRange.from_string( + f"vers:maven/{affected_version}" + ), + fixed_version=fixed_version, + ) + affected_packages.append(affected_package) return affected_packages @@ -166,7 +233,9 @@ def get_fixed_version(fixed_versions, end_version, version_mapping): Get the fixed version from the list of fixed versions. """ for fix_ver in fixed_versions: - if fix_ver in version_mapping and version_mapping.index(fix_ver) >= version_mapping.index(end_version): + if fix_ver in version_mapping and version_mapping.index( + fix_ver + ) >= version_mapping.index(end_version): return fix_ver return None @@ -178,6 +247,6 @@ def advisory_data(self): if not xml_content: logger.error("No XML content fetched.") return [] - + advisories = self.to_advisory(xml_content) - return advisories \ No newline at end of file + return advisories diff --git a/vulnerabilities/improvers/valid_versions.py b/vulnerabilities/improvers/valid_versions.py index ef7226e95..bd7ae8ad1 100644 --- a/vulnerabilities/improvers/valid_versions.py +++ b/vulnerabilities/improvers/valid_versions.py @@ -26,8 +26,8 @@ from vulnerabilities.importer import UnMergeablePackageError from vulnerabilities.importers.apache_httpd import ApacheHTTPDImporter from vulnerabilities.importers.apache_kafka import ApacheKafkaImporter -from vulnerabilities.importers.apache_tomcat import ApacheTomcatImporter from vulnerabilities.importers.apache_log4j import ApacheLog4jImporter +from vulnerabilities.importers.apache_tomcat import ApacheTomcatImporter from vulnerabilities.importers.curl import CurlImporter from vulnerabilities.importers.debian import DebianImporter from vulnerabilities.importers.debian_oval import DebianOvalImporter @@ -486,4 +486,4 @@ class CurlImprover(ValidVersionImprover): class Log4jImprover(ValidVersionImprover): importer = ApacheLog4jImporter - ignorable_versions = [] \ No newline at end of file + ignorable_versions = [] From 32c9345ecd373a45777ce2d9b1e34ed5fbc8b77a Mon Sep 17 00:00:00 2001 From: NucleonGodX Date: Mon, 27 Jan 2025 22:49:40 +0530 Subject: [PATCH 3/5] suggestions applied, importer converted to pipeline Signed-off-by: NucleonGodX --- testt.py | 8 + vulnerabilities/importers/__init__.py | 4 +- vulnerabilities/importers/apache_log4j.py | 252 ----------- vulnerabilities/improvers/valid_versions.py | 4 +- .../pipelines/apache_log4j_importer.py | 298 ++++++++++++ .../test_apache_log4j_importer_pipeline.py | 36 ++ .../tests/test_data/apache_log4j/log4j.xml | 425 ++++++++++++++++++ .../parse-advisory-apache-log4j-expected.json | 332 ++++++++++++++ 8 files changed, 1103 insertions(+), 256 deletions(-) create mode 100644 testt.py delete mode 100644 vulnerabilities/importers/apache_log4j.py create mode 100644 vulnerabilities/pipelines/apache_log4j_importer.py create mode 100644 vulnerabilities/tests/pipelines/test_apache_log4j_importer_pipeline.py create mode 100644 vulnerabilities/tests/test_data/apache_log4j/log4j.xml create mode 100644 vulnerabilities/tests/test_data/apache_log4j/parse-advisory-apache-log4j-expected.json diff --git a/testt.py b/testt.py new file mode 100644 index 000000000..59cb17a81 --- /dev/null +++ b/testt.py @@ -0,0 +1,8 @@ +import os + +import django + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vulnerablecode.settings") +django.setup() + +print("Django initialized successfully") diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 1dd887d33..bdfc6ffbc 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -9,7 +9,6 @@ from vulnerabilities.importers import apache_httpd from vulnerabilities.importers import apache_kafka -from vulnerabilities.importers import apache_log4j from vulnerabilities.importers import apache_tomcat from vulnerabilities.importers import archlinux from vulnerabilities.importers import curl @@ -36,6 +35,7 @@ from vulnerabilities.importers import xen from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipeline from vulnerabilities.pipelines import alpine_linux_importer +from vulnerabilities.pipelines import apache_log4j_importer from vulnerabilities.pipelines import github_importer from vulnerabilities.pipelines import gitlab_importer from vulnerabilities.pipelines import nginx_importer @@ -61,7 +61,7 @@ suse_scores.SUSESeverityScoreImporter, elixir_security.ElixirSecurityImporter, apache_tomcat.ApacheTomcatImporter, - apache_log4j.ApacheLog4jImporter, + apache_log4j_importer.ApacheLog4jImporterPipeline, xen.XenImporter, ubuntu_usn.UbuntuUSNImporter, fireeye.FireyeImporter, diff --git a/vulnerabilities/importers/apache_log4j.py b/vulnerabilities/importers/apache_log4j.py deleted file mode 100644 index 968b6ad3d..000000000 --- a/vulnerabilities/importers/apache_log4j.py +++ /dev/null @@ -1,252 +0,0 @@ -import logging -import xml.etree.ElementTree as ET - -import pytz -import requests -from dateutil.parser import parse -from packageurl import PackageURL -from univers.version_range import MavenVersionRange - -from vulnerabilities.importer import AdvisoryData -from vulnerabilities.importer import AffectedPackage -from vulnerabilities.importer import Importer -from vulnerabilities.importer import Reference - -logger = logging.getLogger(__name__) - - -class ApacheLog4jImporter(Importer): - XML_URL = "https://logging.apache.org/cyclonedx/vdr.xml" - ASF_PAGE_URL = "https://logging.apache.org/security.html" - spdx_license_expression = "Apache-2.0" - license_url = "https://www.apache.org/licenses/" - importer_name = "Apache Log4j Importer" - - @staticmethod - def fetch_advisory_page(): - """ - Fetch the Log4j vulnerability XML feed. - """ - try: - response = requests.get(ApacheLog4jImporter.XML_URL, timeout=30) - response.raise_for_status() - return response.content - except requests.RequestException as e: - logger.error(f"Failed to fetch Log4j vulnerability data: {e}") - return None - - @staticmethod - def parse_version_range(range_str): - """ - Parse version range string and return start and end versions. - Example: "vers:maven/>=2.0-beta7|<2.3.2" -> ("2.0-beta7", "2.3.2") - """ - try: - range_parts = range_str.replace("vers:maven/", "").split("|") - start_version = range_parts[0].replace(">=", "").strip() - end_version = range_parts[1].replace("<", "").strip() - return start_version, end_version - except Exception as e: - logger.error(f"Error parsing version range {range_str}: {e}") - return None, None - - @staticmethod - def get_versions_in_range(start_version, end_version, version_mapping): - """ - Get all versions between start_version and end_version from version_mapping. - """ - try: - start_idx = version_mapping.index(start_version) - end_idx = version_mapping.index(end_version) - return version_mapping[start_idx:end_idx] - except ValueError as e: - logger.error(f"Error getting versions in range: {e}") - return [] - - def to_advisory(self, xml_content): - """ - Parse the XML content and create AdvisoryData objects. - """ - advisories = [] - version_mapping = [ - "2.0-beta1", - "2.0-beta2", - "2.0-beta3", - "2.0-beta5", - "2.0-alpha1", - "2.0-beta7", - "2.0-beta8", - "2.0-beta9", - "2.0-rc1", - "2.0-beta4", - "2.0-beta6", - "2.0-rc2", - "2.0", - "2.0.1", - "2.0.2", - "2.1", - "2.2", - "2.3", - "2.3.1", - "2.3.2", - "2.4", - "2.4.1", - "2.5", - "2.6", - "2.6.1", - "2.6.2", - "2.7", - "2.8", - "2.8.1", - "2.8.2", - "2.9.0", - "2.9.1", - "2.10.0", - "2.11.0", - "2.11.1", - "2.11.2", - "2.12.0", - "2.12.1", - "2.12.2", - "2.12.3", - "2.12.4", - "2.13.0", - "2.13.1", - "2.13.2", - "2.13.3", - "2.14.0", - "2.14.1", - "2.15.0", - "2.15.1", - "2.16.0", - "2.17.0", - "2.17.1", - ] - - try: - root = ET.fromstring(xml_content) - ns = {"ns0": "http://cyclonedx.org/schema/bom/1.5"} - vulnerabilities = root.findall(".//ns0:vulnerability", ns) - - for vuln in vulnerabilities: - cve_id = vuln.find("./ns0:id", ns) - if cve_id is None or not cve_id.text: - continue - cve_id = cve_id.text.strip() - - description = vuln.find("./ns0:description", ns) - description_text = description.text.strip() if description is not None else "" - - published_date_text = vuln.find("./ns0:published", ns) - date_published = None - if published_date_text is not None and published_date_text.text: - try: - date_published = parse(published_date_text.text.strip()).replace( - tzinfo=pytz.UTC - ) - except ValueError as e: - logger.error(f"Error parsing date {published_date_text.text}: {e}") - - references = [ - Reference( - url=f"https://nvd.nist.gov/vuln/detail/{cve_id}", reference_id=cve_id - ), - Reference(url=f"{self.ASF_PAGE_URL}#{cve_id}", reference_id=cve_id), - ] - - affected_packages = self.get_affected_packages(vuln, version_mapping) - - advisories.append( - AdvisoryData( - aliases=[cve_id], - summary=description_text, - affected_packages=affected_packages, - references=references, - date_published=date_published, - url=f"{self.ASF_PAGE_URL}#{cve_id}", - ) - ) - except ET.ParseError as e: - logger.error(f"Failed to parse XML content: {e}") - - return advisories - - def get_affected_packages(self, vuln, version_mapping): - """ - Extract affected packages from the vulnerability element. - """ - ns = {"ns0": "http://cyclonedx.org/schema/bom/1.5"} - affected_packages = [] - - recommendation = vuln.find(".//ns0:recommendation", ns) - fixed_versions = [] - if recommendation is not None and recommendation.text: - fixed_versions = [ - v.strip("`") - for v in recommendation.text.split("`") - if v.strip().replace(".", "").replace("-", "").isalnum() - ] - - targets = vuln.findall(".//ns0:target", ns) - for target in targets: - ref = target.find("./ns0:ref", ns) - if ref is None or not ref.text: - continue - - version_ranges = target.findall("./ns0:versions/ns0:version/ns0:range", ns) - for version_range in version_ranges: - if version_range is None or not version_range.text: - continue - - start_version, end_version = self.parse_version_range(version_range.text.strip()) - if not start_version or not end_version: - continue - - affected_versions = self.get_versions_in_range( - start_version, end_version, version_mapping - ) - if not affected_versions: - continue - - fixed_version = self.get_fixed_version(fixed_versions, end_version, version_mapping) - if not fixed_version: - continue - - for affected_version in affected_versions: - affected_package = AffectedPackage( - package=PackageURL( - type="apache", - name="log4j-core", - ), - affected_version_range=MavenVersionRange.from_string( - f"vers:maven/{affected_version}" - ), - fixed_version=fixed_version, - ) - affected_packages.append(affected_package) - - return affected_packages - - @staticmethod - def get_fixed_version(fixed_versions, end_version, version_mapping): - """ - Get the fixed version from the list of fixed versions. - """ - for fix_ver in fixed_versions: - if fix_ver in version_mapping and version_mapping.index( - fix_ver - ) >= version_mapping.index(end_version): - return fix_ver - return None - - def advisory_data(self): - """ - Fetch advisory data and convert it into AdvisoryData objects. - """ - xml_content = self.fetch_advisory_page() - if not xml_content: - logger.error("No XML content fetched.") - return [] - - advisories = self.to_advisory(xml_content) - return advisories diff --git a/vulnerabilities/improvers/valid_versions.py b/vulnerabilities/improvers/valid_versions.py index bd7ae8ad1..240843621 100644 --- a/vulnerabilities/improvers/valid_versions.py +++ b/vulnerabilities/improvers/valid_versions.py @@ -26,7 +26,6 @@ from vulnerabilities.importer import UnMergeablePackageError from vulnerabilities.importers.apache_httpd import ApacheHTTPDImporter from vulnerabilities.importers.apache_kafka import ApacheKafkaImporter -from vulnerabilities.importers.apache_log4j import ApacheLog4jImporter from vulnerabilities.importers.apache_tomcat import ApacheTomcatImporter from vulnerabilities.importers.curl import CurlImporter from vulnerabilities.importers.debian import DebianImporter @@ -42,6 +41,7 @@ from vulnerabilities.improver import Inference from vulnerabilities.models import Advisory from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipeline +from vulnerabilities.pipelines.apache_log4j_importer import ApacheLog4jImporterPipeline from vulnerabilities.pipelines.github_importer import GitHubAPIImporterPipeline from vulnerabilities.pipelines.gitlab_importer import GitLabImporterPipeline from vulnerabilities.pipelines.nginx_importer import NginxImporterPipeline @@ -485,5 +485,5 @@ class CurlImprover(ValidVersionImprover): class Log4jImprover(ValidVersionImprover): - importer = ApacheLog4jImporter + importer = ApacheLog4jImporterPipeline ignorable_versions = [] diff --git a/vulnerabilities/pipelines/apache_log4j_importer.py b/vulnerabilities/pipelines/apache_log4j_importer.py new file mode 100644 index 000000000..1b582f464 --- /dev/null +++ b/vulnerabilities/pipelines/apache_log4j_importer.py @@ -0,0 +1,298 @@ +# +# 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/aboutcode-org/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import logging +import re +from collections import defaultdict +from typing import Iterable + +import pytz +from cyclonedx.model.bom import Bom +from dateutil.parser import parse +from defusedxml import ElementTree as SafeElementTree +from packageurl import PackageURL +from univers.versions import MavenVersion + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import AffectedPackage +from vulnerabilities.importer import Reference +from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipeline +from vulnerabilities.utils import fetch_response + +logger = logging.getLogger(__name__) + + +class ApacheLog4jImporterPipeline(VulnerableCodeBaseImporterPipeline): + """ + Import security advisories from Apache Log4j's security database. + """ + + pipeline_id = "apache_log4j_importer" + XML_URL = "https://logging.apache.org/cyclonedx/vdr.xml" + ASF_PAGE_URL = "https://logging.apache.org/security.html" + spdx_license_expression = "Apache-2.0" + license_url = "https://www.apache.org/licenses/" + importer_name = "Apache Log4j Importer" + + version_set = [ + "2.0-beta1", + "2.0-beta2", + "2.0-beta3", + "2.0-beta5", + "2.0-alpha1", + "2.0-beta7", + "2.0-beta8", + "2.0-beta9", + "2.0-rc1", + "2.0-beta4", + "2.0-beta6", + "2.0-rc2", + "2.0", + "2.0.1", + "2.0.2", + "2.1", + "2.2", + "2.3", + "2.3.1", + "2.3.2", + "2.4", + "2.4.1", + "2.5", + "2.6", + "2.6.1", + "2.6.2", + "2.7", + "2.8", + "2.8.1", + "2.8.2", + "2.9.0", + "2.9.1", + "2.10.0", + "2.11.0", + "2.11.1", + "2.11.2", + "2.12.0", + "2.12.1", + "2.12.2", + "2.12.3", + "2.12.4", + "2.13.0", + "2.13.1", + "2.13.2", + "2.13.3", + "2.14.0", + "2.14.1", + "2.15.0", + "2.15.1", + "2.16.0", + "2.17.0", + "2.17.1", + ] + + @classmethod + def steps(cls): + """ + Return pipeline steps. + """ + return ( + cls.collect_and_store_advisories, + cls.import_new_advisories, + ) + + def advisories_count(self) -> int: + """ + Return total number of advisories. + """ + return 0 + + def collect_advisories(self) -> Iterable[AdvisoryData]: + """ + Collect Apache Log4j advisories from the CycloneDX VDR file. + """ + xml_content = fetch_response(self.XML_URL).content + if not xml_content: + logger.error("No XML content fetched.") + return [] + + cleaned_xml_data = self._clean_xml_data(xml_content) + if not cleaned_xml_data: + logger.error("Failed to clean XML data") + return [] + + bom = Bom.from_xml(SafeElementTree.fromstring(cleaned_xml_data)) + + for vulnerability in bom.vulnerabilities: + if not vulnerability.id: + continue + + yield from self._process_vulnerability(vulnerability) + + def _clean_xml_data(self, xml_content): + """ + Clean XML data by removing XML schema instance attributes. + """ + root = SafeElementTree.fromstring(xml_content) + for elem in root.iter(): + attribs_to_remove = [ + k for k in elem.attrib if "{http://www.w3.org/2001/XMLSchema-instance}" in k + ] + for attrib in attribs_to_remove: + del elem.attrib[attrib] + return SafeElementTree.tostring(root, encoding="utf-8") + + def _process_vulnerability(self, vulnerability) -> Iterable[AdvisoryData]: + """ + Process a single vulnerability and return AdvisoryData. + """ + cve_id = vulnerability.id + description = vulnerability.description or "" + + date_published = None + if vulnerability.published: + published_str = str(vulnerability.published) + date_published = parse(published_str).replace(tzinfo=pytz.UTC) + + references = [ + Reference(url=f"https://nvd.nist.gov/vuln/detail/{cve_id}", reference_id=cve_id), + Reference(url=f"{self.ASF_PAGE_URL}#{cve_id}", reference_id=cve_id), + ] + + fixed_versions = self._extract_fixed_versions(vulnerability.recommendation) + affected_packages = self._get_affected_packages(vulnerability, fixed_versions) + + if affected_packages: + yield AdvisoryData( + aliases=[cve_id], + summary=description, + affected_packages=affected_packages, + references=references, + date_published=date_published, + url=f"{self.ASF_PAGE_URL}#{cve_id}", + ) + + def _extract_fixed_versions(self, recommendation): + """ + Extract fixed versions from recommendation text. + """ + if not recommendation: + return [] + + recommendation_str = str(recommendation) + version_pattern = r"\b(2\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9]+)?)\b" + found_versions = re.findall(version_pattern, recommendation_str) + + valid_versions = [ver for ver in found_versions if ver in self.version_set] + + return list(dict.fromkeys(valid_versions)) + + def _get_affected_packages(self, vulnerability, fixed_versions): + """ + Get affected packages for a vulnerability. + """ + version_groups = defaultdict(list) + + for vuln_target in vulnerability.affects: + for version_range in vuln_target.versions: + if version_range.version and not version_range.range: + self._process_single_version(version_range, fixed_versions, version_groups) + elif version_range.range: + self._process_version_range(version_range, fixed_versions, version_groups) + + affected_packages = [] + for fixed_version, versions in version_groups.items(): + unique_versions = sorted(set(versions), key=lambda x: MavenVersion(x)) + version_range_str = f"vers:maven/{('|'.join(unique_versions))}" + + affected_packages.append( + AffectedPackage( + package=PackageURL( + type="apache", + name="log4j-core", + ), + affected_version_range=version_range_str, + fixed_version=fixed_version, + ) + ) + + return affected_packages + + def _process_single_version(self, version_range, fixed_versions, version_groups): + """ + Process a single version and add it to version groups. + """ + current_version = version_range.version.replace("vers:maven/", "") + fixed_version = next( + (ver for ver in fixed_versions if MavenVersion(ver) >= MavenVersion(current_version)), + None, + ) + if fixed_version: + version_groups[fixed_version].append(current_version) + + def _process_version_range(self, version_range, fixed_versions, version_groups): + """ + Process a version range and add affected versions to version groups. + """ + start_version, end_version = self._parse_version_range(version_range.range) + if not start_version or not end_version: + return + + affected_versions = self._get_versions_in_range( + start_version, end_version, self.version_set + ) + if not affected_versions: + return + + fixed_version = self._get_fixed_version(fixed_versions, end_version) + if fixed_version: + version_groups[fixed_version].extend(affected_versions) + + def _parse_version_range(self, range_str): + """ + Parse version range string and return start and end versions. + """ + if re.match(r"^vers:maven/\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9]+)?$", range_str): + single_version = range_str.replace("vers:maven/", "").strip() + return single_version, single_version + + range_parts = range_str.replace("vers:maven/", "").split("|") + + if ">=" in range_parts[0] and "<" in range_parts[1]: + start_version = range_parts[0].replace(">=", "").strip() + end_version = range_parts[1].replace("<", "").strip() + return start_version, end_version + + return None, None + + def _get_versions_in_range(self, start_version, end_version, version_set): + """ + Get list of versions between start and end versions. + """ + start_mv = MavenVersion(start_version) + end_mv = MavenVersion(end_version) + + versions_in_range = [ + ver + for ver in version_set + if MavenVersion(ver) >= start_mv and MavenVersion(ver) < end_mv + ] + + return versions_in_range + + def _get_fixed_version(self, fixed_versions, end_version): + """ + Get appropriate fixed version for a given end version. + """ + end_mv = MavenVersion(end_version) + + for fix_ver in fixed_versions: + fix_mv = MavenVersion(fix_ver) + if fix_mv >= end_mv: + return fix_ver + + return None diff --git a/vulnerabilities/tests/pipelines/test_apache_log4j_importer_pipeline.py b/vulnerabilities/tests/pipelines/test_apache_log4j_importer_pipeline.py new file mode 100644 index 000000000..bdd6bf2c5 --- /dev/null +++ b/vulnerabilities/tests/pipelines/test_apache_log4j_importer_pipeline.py @@ -0,0 +1,36 @@ +# +# 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/aboutcode-org/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import os +from pathlib import Path + +from cyclonedx.model.bom import Bom +from defusedxml import ElementTree as SafeElementTree + +from vulnerabilities.pipelines.apache_log4j_importer import ApacheLog4jImporterPipeline +from vulnerabilities.tests import util_tests + +TEST_DATA = Path(__file__).parent.parent / "test_data" / "apache_log4j" + + +def test_to_advisories(): + with open(os.path.join(TEST_DATA, "log4j.xml")) as f: + raw_data = f.read() + + importer = ApacheLog4jImporterPipeline() + cleaned_data = importer._clean_xml_data(raw_data) + bom = Bom.from_xml(SafeElementTree.fromstring(cleaned_data)) + advisories = [] + for vulnerability in bom.vulnerabilities: + advisories.extend(importer._process_vulnerability(vulnerability)) + + result = [data.to_dict() for data in advisories] + + expected_file = os.path.join(TEST_DATA, "parse-advisory-apache-log4j-expected.json") + util_tests.check_results_against_json(result, expected_file) diff --git a/vulnerabilities/tests/test_data/apache_log4j/log4j.xml b/vulnerabilities/tests/test_data/apache_log4j/log4j.xml new file mode 100644 index 000000000..355e99ce0 --- /dev/null +++ b/vulnerabilities/tests/test_data/apache_log4j/log4j.xml @@ -0,0 +1,425 @@ + + + + + + org.apache.logging.log4j + log4j-core + cpe:2.3:a:apache:log4j:*:*:*:*:*:*:*:* + pkg:maven/org.apache.logging.log4j/log4j-core?type=jar + + + + + + + CVE-2021-44832 + + NVD + https://nvd.nist.gov/vuln/detail/CVE-2021-44832 + + + + + NVD + + + + 6.6 + medium + CVSSv3 + AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H + + + + 20 + 74 + + + + 2021-12-28T00:00:00Z + 2021-12-28T00:00:00Z + 2022-08-08T00:00:00Z + + + pkg:maven/org.apache.logging.log4j/log4j-core?type=jar + + + =2.0-beta7|<2.3.2]]> + + + =2.4|<2.12.4]]> + + + =2.13.0|<2.17.1]]> + + + + + + + + CVE-2021-45105 + + NVD + https://nvd.nist.gov/vuln/detail/CVE-2021-45105 + + + + LOG4J2-3230 + + Issue tracker + https://issues.apache.org/jira/browse/LOG4J2-3230 + + + + + + + NVD + + + + 5.9 + medium + CVSSv3 + AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H + + + + 20 + 674 + + + + 2021-12-18T00:00:00Z + 2021-12-18T00:00:00Z + 2022-10-06T00:00:00Z + + + + Hideki Okamoto + + + Guy Lederfein + + + + + + pkg:maven/org.apache.logging.log4j/log4j-core?type=jar + + + =2.0-alpha1|<2.3.1]]> + + + =2.4|<2.12.3]]> + + + =2.13.0|<2.17.0]]> + + + + + + + + CVE-2021-45046 + + NVD + https://nvd.nist.gov/vuln/detail/CVE-2021-45046 + + + + LOG4J2-3221 + + Issue tracker + https://issues.apache.org/jira/browse/LOG4J2-3221 + + + + + + + NVD + + + + 9.0 + critical + CVSSv3 + AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H + + + + 917 + + + + 2021-12-14T00:00:00Z + 2021-12-14T00:00:00Z + 2023-10-26T00:00:00Z + + + + Kai Mindermann + + + 4ra1n + + + Ash Fox + + + Alvaro Muñoz + + + Tony Torralba + + + Anthony Weems + + + RyotaK + + + + + + pkg:maven/org.apache.logging.log4j/log4j-core?type=jar + + + =2.0-beta9|<2.3.1]]> + + + =2.4|<2.12.3]]> + + + =2.13.0|<2.17.0]]> + + + + + + + + CVE-2021-44228 + + NVD + https://nvd.nist.gov/vuln/detail/CVE-2021-44228 + + + + LOG4J2-3198 + + Issue tracker + https://issues.apache.org/jira/browse/LOG4J2-3198 + + + + LOG4J2-3201 + + Issue tracker + https://issues.apache.org/jira/browse/LOG4J2-3201 + + + + + + + NVD + + + 10.0 + critical + CVSSv3 + AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H + + + + 20 + 400 + 502 + 917 + + + + 2021-12-10T00:00:00Z + 2021-12-10T00:00:00Z + 2023-04-03T00:00:00Z + + + + Chen Zhaojun + + + + + + pkg:maven/org.apache.logging.log4j/log4j-core?type=jar + + + =2.0-beta9|<2.3.1]]> + + + =2.4|<2.12.3]]> + + + =2.13.0|<2.17.0]]> + + + + + + + + CVE-2020-9488 + + NVD + https://nvd.nist.gov/vuln/detail/CVE-2020-9488 + + + + LOG4J2-2819 + + Issue tracker + https://issues.apache.org/jira/browse/LOG4J2-2819 + + + + + + + NVD + + + 3.7 + low + CVSSv3 + AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N + + + + 295 + + + + 2017-04-27T00:00:00Z + 2017-04-27T00:00:00Z + 2022-05-12T00:00:00Z + + + + Peter Stöckli + + + + + + pkg:maven/org.apache.logging.log4j/log4j-core?type=jar + + + =2.0-beta1|<2.12.3]]> + + + + + + + + + + + CVE-2017-5645 + + NVD + https://nvd.nist.gov/vuln/detail/CVE-2017-5645 + + + + LOG4J2-1863 + + Issue tracker + https://issues.apache.org/jira/browse/LOG4J2-1863 + + + + the security fix commit + + Source code repository + https://github.com/apache/logging-log4j2/commit/5dcc192 + + + + + + + NVD + + + 7.5 + high + CVSSv2 + AV:N/AC:L/Au:N/C:P/I:P/A:P + + + + 502 + + + + 2017-04-17T00:00:00Z + 2017-04-17T00:00:00Z + 2022-04-04T00:00:00Z + + + + Marcio Almeida de Macedo + + + + + + pkg:maven/org.apache.logging.log4j/log4j-core?type=jar + + + =2.0-alpha1|<2.8.2]]> + + + + + + + + + \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/apache_log4j/parse-advisory-apache-log4j-expected.json b/vulnerabilities/tests/test_data/apache_log4j/parse-advisory-apache-log4j-expected.json new file mode 100644 index 000000000..a20e8402b --- /dev/null +++ b/vulnerabilities/tests/test_data/apache_log4j/parse-advisory-apache-log4j-expected.json @@ -0,0 +1,332 @@ +[ + { + "aliases": [ + "CVE-2017-5645" + ], + "summary": "When using the TCP socket server or UDP socket server to receive serialized log events from another application, a specially crafted binary payload can be sent that, when deserialized, can execute arbitrary code.", + "affected_packages": [ + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.0-alpha1|2.0-beta1|2.0-beta2|2.0-beta3|2.0-beta4|2.0-beta5|2.0-beta6|2.0-beta7|2.0-beta8|2.0-beta9|2.0-rc1|2.0-rc2|2.0|2.0.1|2.0.2|2.1|2.2|2.3|2.3.1|2.3.2|2.4|2.4.1|2.5|2.6|2.6.1|2.6.2|2.7|2.8|2.8.1", + "fixed_version": "2.8.2" + } + ], + "references": [ + { + "reference_id": "CVE-2017-5645", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-5645", + "severities": [] + }, + { + "reference_id": "CVE-2017-5645", + "reference_type": "", + "url": "https://logging.apache.org/security.html#CVE-2017-5645", + "severities": [] + } + ], + "date_published": "2017-04-17T00:00:00+00:00", + "weaknesses": [], + "url": "https://logging.apache.org/security.html#CVE-2017-5645" + }, + { + "aliases": [ + "CVE-2020-9488" + ], + "summary": "Improper validation of certificate with host mismatch in SMTP appender.\nThis could allow an SMTPS connection to be intercepted by a man-in-the-middle attack which could leak any log\nmessages sent through that appender.\n\nThe reported issue was caused by an error in `SslConfiguration`.\nAny element using `SslConfiguration` in the Log4j `Configuration` is also affected by this issue.\nThis includes `HttpAppender`, `SocketAppender`, and `SyslogAppender`.\nUsages of `SslConfiguration` that are configured via system properties are not affected.", + "affected_packages": [ + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.13.1", + "fixed_version": "2.13.2" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.0-beta1|2.0-beta2|2.0-beta3|2.0-beta4|2.0-beta5|2.0-beta6|2.0-beta7|2.0-beta8|2.0-beta9|2.0-rc1|2.0-rc2|2.0|2.0.1|2.0.2|2.1|2.2|2.3|2.3.1|2.3.2|2.4|2.4.1|2.5|2.6|2.6.1|2.6.2|2.7|2.8|2.8.1|2.8.2|2.9.0|2.9.1|2.10.0|2.11.0|2.11.1|2.11.2|2.12.0|2.12.1|2.12.2", + "fixed_version": "2.12.3" + } + ], + "references": [ + { + "reference_id": "CVE-2020-9488", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-9488", + "severities": [] + }, + { + "reference_id": "CVE-2020-9488", + "reference_type": "", + "url": "https://logging.apache.org/security.html#CVE-2020-9488", + "severities": [] + } + ], + "date_published": "2017-04-27T00:00:00+00:00", + "weaknesses": [], + "url": "https://logging.apache.org/security.html#CVE-2020-9488" + }, + { + "aliases": [ + "CVE-2021-44228" + ], + "summary": "In Log4j, the JNDI features used in configurations, log messages, and parameters do not protect against attacker-controlled LDAP and other JNDI related endpoints.\nAn attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers.", + "affected_packages": [ + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.0-beta9|2.0-rc1|2.0-rc2|2.0|2.0.1|2.0.2|2.1|2.2|2.3", + "fixed_version": "2.3.1" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.13.0|2.13.1|2.13.2|2.13.3|2.14.0|2.14.1|2.15.0|2.15.1|2.16.0", + "fixed_version": "2.17.0" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.4|2.4.1|2.5|2.6|2.6.1|2.6.2|2.7|2.8|2.8.1|2.8.2|2.9.0|2.9.1|2.10.0|2.11.0|2.11.1|2.11.2|2.12.0|2.12.1|2.12.2", + "fixed_version": "2.12.3" + } + ], + "references": [ + { + "reference_id": "CVE-2021-44228", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-44228", + "severities": [] + }, + { + "reference_id": "CVE-2021-44228", + "reference_type": "", + "url": "https://logging.apache.org/security.html#CVE-2021-44228", + "severities": [] + } + ], + "date_published": "2021-12-10T00:00:00+00:00", + "weaknesses": [], + "url": "https://logging.apache.org/security.html#CVE-2021-44228" + }, + { + "aliases": [ + "CVE-2021-44832" + ], + "summary": "An attacker with write access to the logging configuration can construct a malicious configuration using a JDBC Appender with a data source referencing a JNDI URI which can execute remote code.\nThis issue is fixed by limiting JNDI data source names to the `java` protocol.", + "affected_packages": [ + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.0-beta7|2.0-beta8|2.0-beta9|2.0-rc1|2.0-rc2|2.0|2.0.1|2.0.2|2.1|2.2|2.3|2.3.1", + "fixed_version": "2.3.2" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.13.0|2.13.1|2.13.2|2.13.3|2.14.0|2.14.1|2.15.0|2.15.1|2.16.0|2.17.0", + "fixed_version": "2.17.1" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.4|2.4.1|2.5|2.6|2.6.1|2.6.2|2.7|2.8|2.8.1|2.8.2|2.9.0|2.9.1|2.10.0|2.11.0|2.11.1|2.11.2|2.12.0|2.12.1|2.12.2|2.12.3", + "fixed_version": "2.12.4" + } + ], + "references": [ + { + "reference_id": "CVE-2021-44832", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-44832", + "severities": [] + }, + { + "reference_id": "CVE-2021-44832", + "reference_type": "", + "url": "https://logging.apache.org/security.html#CVE-2021-44832", + "severities": [] + } + ], + "date_published": "2021-12-28T00:00:00+00:00", + "weaknesses": [], + "url": "https://logging.apache.org/security.html#CVE-2021-44832" + }, + { + "aliases": [ + "CVE-2021-45046" + ], + "summary": "It was found that the fix to address CVE-2021-44228 in Log4j `2.15.0` was incomplete in certain non-default configurations.\nWhen the logging configuration uses a non-default Pattern Layout with a Thread Context Lookup (for example, `$${ctx:loginId}`), attackers with control over Thread Context Map (MDC) can craft malicious input data using a JNDI Lookup pattern, resulting in an information leak and remote code execution in some environments and local code execution in all environments.\nRemote code execution has been demonstrated on macOS, Fedora, Arch Linux, and Alpine Linux.\n\nNote that this vulnerability is not limited to just the JNDI lookup.\nAny other Lookup could also be included in a Thread Context Map variable and possibly have private details exposed to anyone with access to the logs.", + "affected_packages": [ + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.0-beta9|2.0-rc1|2.0-rc2|2.0|2.0.1|2.0.2|2.1|2.2|2.3", + "fixed_version": "2.3.1" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.13.0|2.13.1|2.13.2|2.13.3|2.14.0|2.14.1|2.15.0|2.15.1|2.16.0", + "fixed_version": "2.17.0" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.4|2.4.1|2.5|2.6|2.6.1|2.6.2|2.7|2.8|2.8.1|2.8.2|2.9.0|2.9.1|2.10.0|2.11.0|2.11.1|2.11.2|2.12.0|2.12.1|2.12.2", + "fixed_version": "2.12.3" + } + ], + "references": [ + { + "reference_id": "CVE-2021-45046", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-45046", + "severities": [] + }, + { + "reference_id": "CVE-2021-45046", + "reference_type": "", + "url": "https://logging.apache.org/security.html#CVE-2021-45046", + "severities": [] + } + ], + "date_published": "2021-12-14T00:00:00+00:00", + "weaknesses": [], + "url": "https://logging.apache.org/security.html#CVE-2021-45046" + }, + { + "aliases": [ + "CVE-2021-45105" + ], + "summary": "Log4j versions `2.0-alpha1` through `2.16.0` (excluding `2.3.1` and `2.12.3`), did not protect from uncontrolled recursion that can be implemented using self-referential lookups.\nWhen the logging configuration uses a non-default Pattern Layout with a Context Lookup (for example, `$${ctx:loginId}`), attackers with control over Thread Context Map (MDC) input data can craft malicious input data that contains a recursive lookup, resulting in a `StackOverflowError` that will terminate the process.", + "affected_packages": [ + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.0-alpha1|2.0-beta1|2.0-beta2|2.0-beta3|2.0-beta4|2.0-beta5|2.0-beta6|2.0-beta7|2.0-beta8|2.0-beta9|2.0-rc1|2.0-rc2|2.0|2.0.1|2.0.2|2.1|2.2|2.3", + "fixed_version": "2.3.1" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.13.0|2.13.1|2.13.2|2.13.3|2.14.0|2.14.1|2.15.0|2.15.1|2.16.0", + "fixed_version": "2.17.0" + }, + { + "package": { + "type": "apache", + "namespace": "", + "name": "log4j-core", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:maven/2.4|2.4.1|2.5|2.6|2.6.1|2.6.2|2.7|2.8|2.8.1|2.8.2|2.9.0|2.9.1|2.10.0|2.11.0|2.11.1|2.11.2|2.12.0|2.12.1|2.12.2", + "fixed_version": "2.12.3" + } + ], + "references": [ + { + "reference_id": "CVE-2021-45105", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-45105", + "severities": [] + }, + { + "reference_id": "CVE-2021-45105", + "reference_type": "", + "url": "https://logging.apache.org/security.html#CVE-2021-45105", + "severities": [] + } + ], + "date_published": "2021-12-18T00:00:00+00:00", + "weaknesses": [], + "url": "https://logging.apache.org/security.html#CVE-2021-45105" + } +] \ No newline at end of file From 79e83d190d85799761e23372331334a9affc4555 Mon Sep 17 00:00:00 2001 From: NucleonGodX Date: Mon, 27 Jan 2025 22:51:30 +0530 Subject: [PATCH 4/5] clean-up Signed-off-by: NucleonGodX --- testt.py | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 testt.py diff --git a/testt.py b/testt.py deleted file mode 100644 index 59cb17a81..000000000 --- a/testt.py +++ /dev/null @@ -1,8 +0,0 @@ -import os - -import django - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vulnerablecode.settings") -django.setup() - -print("Django initialized successfully") From ee6578a059950c79ee9ee743c682e1a3225da157 Mon Sep 17 00:00:00 2001 From: NucleonGodX Date: Thu, 30 Jan 2025 23:00:37 +0530 Subject: [PATCH 5/5] severity_systems and weaknesses updated for importer Signed-off-by: NucleonGodX --- .../pipelines/apache_log4j_importer.py | 22 ++++ .../parse-advisory-apache-log4j-expected.json | 101 ++++++++++++++++-- 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/vulnerabilities/pipelines/apache_log4j_importer.py b/vulnerabilities/pipelines/apache_log4j_importer.py index 1b582f464..7d45725e0 100644 --- a/vulnerabilities/pipelines/apache_log4j_importer.py +++ b/vulnerabilities/pipelines/apache_log4j_importer.py @@ -19,11 +19,14 @@ from packageurl import PackageURL from univers.versions import MavenVersion +from vulnerabilities import severity_systems from vulnerabilities.importer import AdvisoryData from vulnerabilities.importer import AffectedPackage from vulnerabilities.importer import Reference +from vulnerabilities.importer import VulnerabilitySeverity from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipeline from vulnerabilities.utils import fetch_response +from vulnerabilities.utils import get_cwe_id logger = logging.getLogger(__name__) @@ -157,12 +160,30 @@ def _process_vulnerability(self, vulnerability) -> Iterable[AdvisoryData]: if vulnerability.published: published_str = str(vulnerability.published) date_published = parse(published_str).replace(tzinfo=pytz.UTC) + severities = [] + weaknesses = [] + for cwe in vulnerability.cwes: + cwe_id = cwe + weaknesses.append(get_cwe_id(f"CWE-{cwe_id}")) references = [ Reference(url=f"https://nvd.nist.gov/vuln/detail/{cve_id}", reference_id=cve_id), Reference(url=f"{self.ASF_PAGE_URL}#{cve_id}", reference_id=cve_id), ] + for rating in vulnerability.ratings: + cvssv3_score = str(rating.score) + cvssv3_vector = rating.vector + cvssv3_url = str(rating.source.url) + severities.append( + VulnerabilitySeverity( + system=severity_systems.CVSSV3, + value=cvssv3_score, + scoring_elements=cvssv3_vector, + ) + ) + references.append(Reference(url=cvssv3_url, severities=severities)) + fixed_versions = self._extract_fixed_versions(vulnerability.recommendation) affected_packages = self._get_affected_packages(vulnerability, fixed_versions) @@ -173,6 +194,7 @@ def _process_vulnerability(self, vulnerability) -> Iterable[AdvisoryData]: affected_packages=affected_packages, references=references, date_published=date_published, + weaknesses=weaknesses, url=f"{self.ASF_PAGE_URL}#{cve_id}", ) diff --git a/vulnerabilities/tests/test_data/apache_log4j/parse-advisory-apache-log4j-expected.json b/vulnerabilities/tests/test_data/apache_log4j/parse-advisory-apache-log4j-expected.json index a20e8402b..6bc58dfd0 100644 --- a/vulnerabilities/tests/test_data/apache_log4j/parse-advisory-apache-log4j-expected.json +++ b/vulnerabilities/tests/test_data/apache_log4j/parse-advisory-apache-log4j-expected.json @@ -30,10 +30,24 @@ "reference_type": "", "url": "https://logging.apache.org/security.html#CVE-2017-5645", "severities": [] + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?vector=(AV:N/AC:L/Au:N/C:P/I:P/A:P)&version=2.0", + "severities": [ + { + "system": "cvssv3", + "value": "7.5", + "scoring_elements": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + } + ] } ], "date_published": "2017-04-17T00:00:00+00:00", - "weaknesses": [], + "weaknesses": [ + 502 + ], "url": "https://logging.apache.org/security.html#CVE-2017-5645" }, { @@ -79,10 +93,24 @@ "reference_type": "", "url": "https://logging.apache.org/security.html#CVE-2020-9488", "severities": [] + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N&version=3.1", + "severities": [ + { + "system": "cvssv3", + "value": "3.7", + "scoring_elements": "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N" + } + ] } ], "date_published": "2017-04-27T00:00:00+00:00", - "weaknesses": [], + "weaknesses": [ + 295 + ], "url": "https://logging.apache.org/security.html#CVE-2020-9488" }, { @@ -140,10 +168,27 @@ "reference_type": "", "url": "https://logging.apache.org/security.html#CVE-2021-44228", "severities": [] + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H&version=3.1", + "severities": [ + { + "system": "cvssv3", + "value": "10.0", + "scoring_elements": "AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" + } + ] } ], "date_published": "2021-12-10T00:00:00+00:00", - "weaknesses": [], + "weaknesses": [ + 20, + 400, + 502, + 917 + ], "url": "https://logging.apache.org/security.html#CVE-2021-44228" }, { @@ -201,10 +246,25 @@ "reference_type": "", "url": "https://logging.apache.org/security.html#CVE-2021-44832", "severities": [] + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H&version=3.1", + "severities": [ + { + "system": "cvssv3", + "value": "6.6", + "scoring_elements": "AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H" + } + ] } ], "date_published": "2021-12-28T00:00:00+00:00", - "weaknesses": [], + "weaknesses": [ + 20, + 74 + ], "url": "https://logging.apache.org/security.html#CVE-2021-44832" }, { @@ -262,10 +322,24 @@ "reference_type": "", "url": "https://logging.apache.org/security.html#CVE-2021-45046", "severities": [] + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H&version=3.1", + "severities": [ + { + "system": "cvssv3", + "value": "9.0", + "scoring_elements": "AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H" + } + ] } ], "date_published": "2021-12-14T00:00:00+00:00", - "weaknesses": [], + "weaknesses": [ + 917 + ], "url": "https://logging.apache.org/security.html#CVE-2021-45046" }, { @@ -323,10 +397,25 @@ "reference_type": "", "url": "https://logging.apache.org/security.html#CVE-2021-45105", "severities": [] + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H&version=3.1", + "severities": [ + { + "system": "cvssv3", + "value": "5.9", + "scoring_elements": "AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ] } ], "date_published": "2021-12-18T00:00:00+00:00", - "weaknesses": [], + "weaknesses": [ + 20, + 674 + ], "url": "https://logging.apache.org/security.html#CVE-2021-45105" } ] \ No newline at end of file