Skip to content

Commit 13427e1

Browse files
authored
Merge pull request #1102 from TG1999/apache_httpd_add_improver
Add apache_httpd improver
2 parents f31a2aa + 136d868 commit 13427e1

File tree

4 files changed

+229
-2
lines changed

4 files changed

+229
-2
lines changed

vulnerabilities/importers/apache_httpd.py

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10-
import asyncio
10+
import logging
1111
import urllib
12+
from datetime import datetime
13+
from typing import Iterable
14+
from typing import List
15+
from typing import Mapping
16+
from typing import Optional
1217

1318
import requests
1419
from bs4 import BeautifulSoup
20+
from django.db.models.query import QuerySet
1521
from packageurl import PackageURL
1622
from univers.version_constraint import VersionConstraint
1723
from univers.version_range import ApacheVersionRange
@@ -21,8 +27,20 @@
2127
from vulnerabilities.importer import AffectedPackage
2228
from vulnerabilities.importer import Importer
2329
from vulnerabilities.importer import Reference
30+
from vulnerabilities.importer import UnMergeablePackageError
2431
from vulnerabilities.importer import VulnerabilitySeverity
32+
from vulnerabilities.improver import Improver
33+
from vulnerabilities.improver import Inference
34+
from vulnerabilities.models import Advisory
35+
from vulnerabilities.package_managers import GitHubTagsAPI
36+
from vulnerabilities.package_managers import VersionAPI
2537
from vulnerabilities.severity_systems import APACHE_HTTPD
38+
from vulnerabilities.utils import AffectedPackage as LegacyAffectedPackage
39+
from vulnerabilities.utils import get_affected_packages_by_patched_package
40+
from vulnerabilities.utils import nearest_patched_package
41+
from vulnerabilities.utils import resolve_version_range
42+
43+
logger = logging.getLogger(__name__)
2644

2745

2846
class ApacheHTTPDImporter(Importer):
@@ -147,7 +165,7 @@ def fetch_links(url):
147165
return links
148166

149167

150-
ignore_tags = {
168+
IGNORE_TAGS = {
151169
"AGB_BEFORE_AAA_CHANGES",
152170
"APACHE_1_2b1",
153171
"APACHE_1_2b10",
@@ -209,3 +227,80 @@ def fetch_links(url):
209227
"post_ajp_proxy",
210228
"pre_ajp_proxy",
211229
}
230+
231+
232+
class ApacheHTTPDImprover(Improver):
233+
def __init__(self) -> None:
234+
self.versions_fetcher_by_purl: Mapping[str, VersionAPI] = {}
235+
self.vesions_by_purl = {}
236+
237+
@property
238+
def interesting_advisories(self) -> QuerySet:
239+
return Advisory.objects.filter(created_by=ApacheHTTPDImporter.qualified_name)
240+
241+
def get_package_versions(
242+
self, package_url: PackageURL, until: Optional[datetime] = None
243+
) -> List[str]:
244+
"""
245+
Return a list of `valid_versions` for the `package_url`
246+
"""
247+
api_name = "apache/httpd"
248+
versions_fetcher = GitHubTagsAPI()
249+
return versions_fetcher.get_until(package_name=api_name, until=until).valid_versions
250+
251+
def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
252+
"""
253+
Yield Inferences for the given advisory data
254+
"""
255+
if not advisory_data.affected_packages:
256+
return
257+
try:
258+
purl, affected_version_ranges, _ = AffectedPackage.merge(
259+
advisory_data.affected_packages
260+
)
261+
except UnMergeablePackageError:
262+
logger.error(f"Cannot merge with different purls {advisory_data.affected_packages!r}")
263+
return iter([])
264+
265+
pkg_type = purl.type
266+
pkg_namespace = purl.namespace
267+
pkg_name = purl.name
268+
269+
if not self.vesions_by_purl.get(str(purl)):
270+
valid_versions = self.get_package_versions(
271+
package_url=purl, until=advisory_data.date_published
272+
)
273+
self.vesions_by_purl[str(purl)] = valid_versions
274+
275+
valid_versions = self.vesions_by_purl[str(purl)]
276+
277+
for affected_version_range in affected_version_ranges:
278+
aff_vers, unaff_vers = resolve_version_range(
279+
affected_version_range=affected_version_range,
280+
package_versions=valid_versions,
281+
ignorable_versions=IGNORE_TAGS,
282+
)
283+
affected_purls = [
284+
PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version)
285+
for version in aff_vers
286+
]
287+
288+
unaffected_purls = [
289+
PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version)
290+
for version in unaff_vers
291+
]
292+
293+
affected_packages: List[LegacyAffectedPackage] = nearest_patched_package(
294+
vulnerable_packages=affected_purls, resolved_packages=unaffected_purls
295+
)
296+
297+
for (
298+
fixed_package,
299+
affected_packages,
300+
) in get_affected_packages_by_patched_package(affected_packages).items():
301+
yield Inference.from_advisory_data(
302+
advisory_data,
303+
confidence=100, # We are getting all valid versions to get this inference
304+
affected_purls=affected_packages,
305+
fixed_purl=fixed_package,
306+
)

vulnerabilities/improvers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
importers.istio.IstioImprover,
2121
oval.DebianOvalBasicImprover,
2222
oval.UbuntuOvalBasicImprover,
23+
importers.apache_httpd.ApacheHTTPDImprover,
2324
]
2425

2526
IMPROVERS_REGISTRY = {x.qualified_name: x for x in IMPROVERS_REGISTRY}

vulnerabilities/tests/test_apache_httpd.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@
99

1010
import json
1111
import os
12+
from unittest import mock
1213

1314
import pytest
1415
from univers.version_constraint import VersionConstraint
1516
from univers.version_range import ApacheVersionRange
1617
from univers.versions import SemverVersion
1718

19+
from vulnerabilities.importer import AdvisoryData
1820
from vulnerabilities.importers.apache_httpd import ApacheHTTPDImporter
21+
from vulnerabilities.importers.apache_httpd import ApacheHTTPDImprover
22+
from vulnerabilities.improvers.default import DefaultImprover
1923
from vulnerabilities.tests import util_tests
2024

2125
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -116,3 +120,24 @@ def test_to_advisory_CVE_2022_28614():
116120
result = advisories.to_dict()
117121
expected_file = os.path.join(TEST_DATA, f"CVE-2022-28614-apache-httpd-expected.json")
118122
util_tests.check_results_against_json(result, expected_file)
123+
124+
125+
@mock.patch("vulnerabilities.importers.apache_httpd.ApacheHTTPDImprover.get_package_versions")
126+
def test_apache_httpd_improver(mock_response):
127+
advisory_file = os.path.join(TEST_DATA, f"CVE-2021-44224-apache-httpd-expected.json")
128+
expected_file = os.path.join(TEST_DATA, f"apache-httpd-improver-expected.json")
129+
with open(advisory_file) as exp:
130+
advisory = AdvisoryData.from_dict(json.load(exp))
131+
mock_response.return_value = [
132+
"2.4.8",
133+
"2.4.9",
134+
"2.4.10",
135+
"2.4.53",
136+
"2.4.54",
137+
]
138+
improvers = [ApacheHTTPDImprover(), DefaultImprover()]
139+
result = []
140+
for improver in improvers:
141+
inference = [data.to_dict() for data in improver.get_inferences(advisory)]
142+
result.extend(inference)
143+
util_tests.check_results_against_json(result, expected_file)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
[
2+
{
3+
"vulnerability_id": null,
4+
"aliases": [
5+
"CVE-2021-44224"
6+
],
7+
"confidence": 100,
8+
"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).",
9+
"affected_purls": [
10+
{
11+
"type": "apache",
12+
"namespace": null,
13+
"name": "httpd",
14+
"version": "2.4.8",
15+
"qualifiers": null,
16+
"subpath": null
17+
},
18+
{
19+
"type": "apache",
20+
"namespace": null,
21+
"name": "httpd",
22+
"version": "2.4.9",
23+
"qualifiers": null,
24+
"subpath": null
25+
},
26+
{
27+
"type": "apache",
28+
"namespace": null,
29+
"name": "httpd",
30+
"version": "2.4.10",
31+
"qualifiers": null,
32+
"subpath": null
33+
}
34+
],
35+
"fixed_purl": {
36+
"type": "apache",
37+
"namespace": null,
38+
"name": "httpd",
39+
"version": "2.4.53",
40+
"qualifiers": null,
41+
"subpath": null
42+
},
43+
"references": [
44+
{
45+
"reference_id": "CVE-2021-44224",
46+
"url": "https://httpd.apache.org/security/json/CVE-2021-44224.json",
47+
"severities": [
48+
{
49+
"system": "apache_httpd",
50+
"value": "moderate",
51+
"scoring_elements": ""
52+
}
53+
]
54+
}
55+
],
56+
"weaknesses": []
57+
},
58+
{
59+
"vulnerability_id": null,
60+
"aliases": [
61+
"CVE-2021-44224"
62+
],
63+
"confidence": 100,
64+
"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).",
65+
"affected_purls": [
66+
{
67+
"type": "apache",
68+
"namespace": null,
69+
"name": "httpd",
70+
"version": "2.4.7",
71+
"qualifiers": null,
72+
"subpath": null
73+
},
74+
{
75+
"type": "apache",
76+
"namespace": null,
77+
"name": "httpd",
78+
"version": "2.4.51",
79+
"qualifiers": null,
80+
"subpath": null
81+
}
82+
],
83+
"fixed_purl": {
84+
"type": "apache",
85+
"namespace": null,
86+
"name": "httpd",
87+
"version": "2.4.52",
88+
"qualifiers": null,
89+
"subpath": null
90+
},
91+
"references": [
92+
{
93+
"reference_id": "CVE-2021-44224",
94+
"url": "https://httpd.apache.org/security/json/CVE-2021-44224.json",
95+
"severities": [
96+
{
97+
"system": "apache_httpd",
98+
"value": "moderate",
99+
"scoring_elements": ""
100+
}
101+
]
102+
}
103+
],
104+
"weaknesses": []
105+
}
106+
]

0 commit comments

Comments
 (0)