From 522ab6a63681bb0e7b63616a783c00ee630de74b Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Thu, 26 Jan 2023 19:46:09 +0530 Subject: [PATCH 1/2] Add apache_httpd improver Signed-off-by: Tushar Goel --- vulnerabilities/importers/apache_httpd.py | 99 ++++++++++++++++++++++- vulnerabilities/improvers/__init__.py | 1 + 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/vulnerabilities/importers/apache_httpd.py b/vulnerabilities/importers/apache_httpd.py index cadcd6800..6f120addd 100644 --- a/vulnerabilities/importers/apache_httpd.py +++ b/vulnerabilities/importers/apache_httpd.py @@ -7,11 +7,17 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -import asyncio +import logging import urllib +from datetime import datetime +from typing import Iterable +from typing import List +from typing import Mapping +from typing import Optional import requests from bs4 import BeautifulSoup +from django.db.models.query import QuerySet from packageurl import PackageURL from univers.version_constraint import VersionConstraint from univers.version_range import ApacheVersionRange @@ -21,8 +27,20 @@ from vulnerabilities.importer import AffectedPackage from vulnerabilities.importer import Importer from vulnerabilities.importer import Reference +from vulnerabilities.importer import UnMergeablePackageError from vulnerabilities.importer import VulnerabilitySeverity +from vulnerabilities.improver import Improver +from vulnerabilities.improver import Inference +from vulnerabilities.models import Advisory +from vulnerabilities.package_managers import GitHubTagsAPI +from vulnerabilities.package_managers import VersionAPI from vulnerabilities.severity_systems import APACHE_HTTPD +from vulnerabilities.utils import AffectedPackage as LegacyAffectedPackage +from vulnerabilities.utils import get_affected_packages_by_patched_package +from vulnerabilities.utils import nearest_patched_package +from vulnerabilities.utils import resolve_version_range + +logger = logging.getLogger(__name__) class ApacheHTTPDImporter(Importer): @@ -147,7 +165,7 @@ def fetch_links(url): return links -ignore_tags = { +IGNORE_TAGS = { "AGB_BEFORE_AAA_CHANGES", "APACHE_1_2b1", "APACHE_1_2b10", @@ -209,3 +227,80 @@ def fetch_links(url): "post_ajp_proxy", "pre_ajp_proxy", } + + +class ApacheHTTPDImprover(Improver): + def __init__(self) -> None: + self.versions_fetcher_by_purl: Mapping[str, VersionAPI] = {} + self.vesions_by_purl = {} + + @property + def interesting_advisories(self) -> QuerySet: + return Advisory.objects.filter(created_by=ApacheHTTPDImporter.qualified_name) + + def get_package_versions( + self, package_url: PackageURL, until: Optional[datetime] = None + ) -> List[str]: + """ + Return a list of `valid_versions` for the `package_url` + """ + api_name = "apache/httpd" + versions_fetcher = GitHubTagsAPI() + return versions_fetcher.get_until(package_name=api_name, until=until).valid_versions + + def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]: + """ + Yield Inferences for the given advisory data + """ + if not advisory_data.affected_packages: + return + try: + purl, affected_version_ranges, _ = AffectedPackage.merge( + advisory_data.affected_packages + ) + except UnMergeablePackageError: + logger.error(f"Cannot merge with different purls {advisory_data.affected_packages!r}") + return iter([]) + + pkg_type = purl.type + pkg_namespace = purl.namespace + pkg_name = purl.name + + if not self.vesions_by_purl.get(str(purl)): + valid_versions = self.get_package_versions( + package_url=purl, until=advisory_data.date_published + ) + self.vesions_by_purl[str(purl)] = valid_versions + + valid_versions = self.vesions_by_purl[str(purl)] + + for affected_version_range in affected_version_ranges: + aff_vers, unaff_vers = resolve_version_range( + affected_version_range=affected_version_range, + package_versions=valid_versions, + ignorable_versions=IGNORE_TAGS, + ) + affected_purls = [ + PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version) + for version in aff_vers + ] + + unaffected_purls = [ + PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version) + for version in unaff_vers + ] + + affected_packages: List[LegacyAffectedPackage] = nearest_patched_package( + vulnerable_packages=affected_purls, resolved_packages=unaffected_purls + ) + + for ( + fixed_package, + affected_packages, + ) in get_affected_packages_by_patched_package(affected_packages).items(): + yield Inference.from_advisory_data( + advisory_data, + confidence=100, # We are getting all valid versions to get this inference + affected_purls=affected_packages, + fixed_purl=fixed_package, + ) diff --git a/vulnerabilities/improvers/__init__.py b/vulnerabilities/improvers/__init__.py index 86a3525ef..50e8ead0a 100644 --- a/vulnerabilities/improvers/__init__.py +++ b/vulnerabilities/improvers/__init__.py @@ -20,6 +20,7 @@ importers.istio.IstioImprover, oval.DebianOvalBasicImprover, oval.UbuntuOvalBasicImprover, + importers.apache_httpd.ApacheHTTPDImprover, ] IMPROVERS_REGISTRY = {x.qualified_name: x for x in IMPROVERS_REGISTRY} From 136d86857a88a1bd041b53180c938429bc6ed6ec Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Thu, 26 Jan 2023 23:35:10 +0530 Subject: [PATCH 2/2] Add tests for apache httpd improver Signed-off-by: Tushar Goel --- vulnerabilities/tests/test_apache_httpd.py | 25 +++++ .../apache-httpd-improver-expected.json | 106 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 vulnerabilities/tests/test_data/apache_httpd/apache-httpd-improver-expected.json diff --git a/vulnerabilities/tests/test_apache_httpd.py b/vulnerabilities/tests/test_apache_httpd.py index 9ff6bd5b6..48d0a028e 100644 --- a/vulnerabilities/tests/test_apache_httpd.py +++ b/vulnerabilities/tests/test_apache_httpd.py @@ -9,13 +9,17 @@ import json import os +from unittest import mock import pytest from univers.version_constraint import VersionConstraint from univers.version_range import ApacheVersionRange from univers.versions import SemverVersion +from vulnerabilities.importer import AdvisoryData from vulnerabilities.importers.apache_httpd import ApacheHTTPDImporter +from vulnerabilities.importers.apache_httpd import ApacheHTTPDImprover +from vulnerabilities.improvers.default import DefaultImprover from vulnerabilities.tests import util_tests BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -116,3 +120,24 @@ def test_to_advisory_CVE_2022_28614(): result = advisories.to_dict() expected_file = os.path.join(TEST_DATA, f"CVE-2022-28614-apache-httpd-expected.json") util_tests.check_results_against_json(result, expected_file) + + +@mock.patch("vulnerabilities.importers.apache_httpd.ApacheHTTPDImprover.get_package_versions") +def test_apache_httpd_improver(mock_response): + advisory_file = os.path.join(TEST_DATA, f"CVE-2021-44224-apache-httpd-expected.json") + expected_file = os.path.join(TEST_DATA, f"apache-httpd-improver-expected.json") + with open(advisory_file) as exp: + advisory = AdvisoryData.from_dict(json.load(exp)) + mock_response.return_value = [ + "2.4.8", + "2.4.9", + "2.4.10", + "2.4.53", + "2.4.54", + ] + improvers = [ApacheHTTPDImprover(), DefaultImprover()] + result = [] + for improver in improvers: + inference = [data.to_dict() for data in improver.get_inferences(advisory)] + result.extend(inference) + util_tests.check_results_against_json(result, expected_file) diff --git a/vulnerabilities/tests/test_data/apache_httpd/apache-httpd-improver-expected.json b/vulnerabilities/tests/test_data/apache_httpd/apache-httpd-improver-expected.json new file mode 100644 index 000000000..4cffb6aac --- /dev/null +++ b/vulnerabilities/tests/test_data/apache_httpd/apache-httpd-improver-expected.json @@ -0,0 +1,106 @@ +[ + { + "vulnerability_id": null, + "aliases": [ + "CVE-2021-44224" + ], + "confidence": 100, + "summary": "A crafted URI sent to httpd configured as a forward proxy (ProxyRequests on) can cause a crash (NULL pointer dereference) or, for configurations mixing forward and reverse proxy declarations, can allow for requests to be directed to a declared Unix Domain Socket endpoint (Server Side Request Forgery).\n\nThis issue affects Apache HTTP Server 2.4.7 up to 2.4.51 (included).", + "affected_purls": [ + { + "type": "apache", + "namespace": null, + "name": "httpd", + "version": "2.4.8", + "qualifiers": null, + "subpath": null + }, + { + "type": "apache", + "namespace": null, + "name": "httpd", + "version": "2.4.9", + "qualifiers": null, + "subpath": null + }, + { + "type": "apache", + "namespace": null, + "name": "httpd", + "version": "2.4.10", + "qualifiers": null, + "subpath": null + } + ], + "fixed_purl": { + "type": "apache", + "namespace": null, + "name": "httpd", + "version": "2.4.53", + "qualifiers": null, + "subpath": null + }, + "references": [ + { + "reference_id": "CVE-2021-44224", + "url": "https://httpd.apache.org/security/json/CVE-2021-44224.json", + "severities": [ + { + "system": "apache_httpd", + "value": "moderate", + "scoring_elements": "" + } + ] + } + ], + "weaknesses": [] + }, + { + "vulnerability_id": null, + "aliases": [ + "CVE-2021-44224" + ], + "confidence": 100, + "summary": "A crafted URI sent to httpd configured as a forward proxy (ProxyRequests on) can cause a crash (NULL pointer dereference) or, for configurations mixing forward and reverse proxy declarations, can allow for requests to be directed to a declared Unix Domain Socket endpoint (Server Side Request Forgery).\n\nThis issue affects Apache HTTP Server 2.4.7 up to 2.4.51 (included).", + "affected_purls": [ + { + "type": "apache", + "namespace": null, + "name": "httpd", + "version": "2.4.7", + "qualifiers": null, + "subpath": null + }, + { + "type": "apache", + "namespace": null, + "name": "httpd", + "version": "2.4.51", + "qualifiers": null, + "subpath": null + } + ], + "fixed_purl": { + "type": "apache", + "namespace": null, + "name": "httpd", + "version": "2.4.52", + "qualifiers": null, + "subpath": null + }, + "references": [ + { + "reference_id": "CVE-2021-44224", + "url": "https://httpd.apache.org/security/json/CVE-2021-44224.json", + "severities": [ + { + "system": "apache_httpd", + "value": "moderate", + "scoring_elements": "" + } + ] + } + ], + "weaknesses": [] + } +] \ No newline at end of file