Skip to content

Commit 96a52e8

Browse files
authored
Merge pull request #2119 from aboutcode-org/openssl-v2
Add v2 pipeline collect OpenSSL advisory
2 parents 6255cb2 + 94a542a commit 96a52e8

File tree

15 files changed

+1425
-11
lines changed

15 files changed

+1425
-11
lines changed

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
from vulnerabilities.pipelines.v2_importers import nginx_importer as nginx_importer_v2
5959
from vulnerabilities.pipelines.v2_importers import npm_importer as npm_importer_v2
6060
from vulnerabilities.pipelines.v2_importers import nvd_importer as nvd_importer_v2
61+
from vulnerabilities.pipelines.v2_importers import openssl_importer as openssl_importer_v2
6162
from vulnerabilities.pipelines.v2_importers import oss_fuzz as oss_fuzz_v2
6263
from vulnerabilities.pipelines.v2_importers import postgresql_importer as postgresql_importer_v2
6364
from vulnerabilities.pipelines.v2_importers import (
@@ -119,6 +120,7 @@
119120
ruby.RubyImporter,
120121
apache_kafka.ApacheKafkaImporter,
121122
openssl.OpensslImporter,
123+
openssl_importer_v2.OpenSSLImporterPipeline,
122124
redhat.RedhatImporter,
123125
archlinux.ArchlinuxImporter,
124126
ubuntu.UbuntuImporter,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Generated by Django 4.2.25 on 2026-01-19 06:22
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0110_pipelineschedule_is_run_once"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="advisoryseverity",
15+
name="scoring_system",
16+
field=models.CharField(
17+
choices=[
18+
("cvssv2", "CVSSv2 Base Score"),
19+
("cvssv3", "CVSSv3 Base Score"),
20+
("cvssv3.1", "CVSSv3.1 Base Score"),
21+
("cvssv4", "CVSSv4 Base Score"),
22+
("rhbs", "RedHat Bugzilla severity"),
23+
("rhas", "RedHat Aggregate severity"),
24+
("archlinux", "Archlinux Vulnerability Group Severity"),
25+
("cvssv3.1_qr", "CVSSv3.1 Qualitative Severity Rating"),
26+
("generic_textual", "Generic textual severity rating"),
27+
("apache_httpd", "Apache Httpd Severity"),
28+
("apache_tomcat", "Apache Tomcat Severity"),
29+
("epss", "Exploit Prediction Scoring System"),
30+
("ssvc", "Stakeholder-Specific Vulnerability Categorization"),
31+
("openssl", "OpenSSL Severity"),
32+
],
33+
help_text="Identifier for the scoring system used. Available choices are: cvssv2: CVSSv2 Base Score,\ncvssv3: CVSSv3 Base Score,\ncvssv3.1: CVSSv3.1 Base Score,\ncvssv4: CVSSv4 Base Score,\nrhbs: RedHat Bugzilla severity,\nrhas: RedHat Aggregate severity,\narchlinux: Archlinux Vulnerability Group Severity,\ncvssv3.1_qr: CVSSv3.1 Qualitative Severity Rating,\ngeneric_textual: Generic textual severity rating,\napache_httpd: Apache Httpd Severity,\napache_tomcat: Apache Tomcat Severity,\nepss: Exploit Prediction Scoring System,\nssvc: Stakeholder-Specific Vulnerability Categorization,\nopenssl: OpenSSL Severity ",
34+
max_length=50,
35+
),
36+
),
37+
migrations.AlterField(
38+
model_name="vulnerabilityseverity",
39+
name="scoring_system",
40+
field=models.CharField(
41+
choices=[
42+
("cvssv2", "CVSSv2 Base Score"),
43+
("cvssv3", "CVSSv3 Base Score"),
44+
("cvssv3.1", "CVSSv3.1 Base Score"),
45+
("cvssv4", "CVSSv4 Base Score"),
46+
("rhbs", "RedHat Bugzilla severity"),
47+
("rhas", "RedHat Aggregate severity"),
48+
("archlinux", "Archlinux Vulnerability Group Severity"),
49+
("cvssv3.1_qr", "CVSSv3.1 Qualitative Severity Rating"),
50+
("generic_textual", "Generic textual severity rating"),
51+
("apache_httpd", "Apache Httpd Severity"),
52+
("apache_tomcat", "Apache Tomcat Severity"),
53+
("epss", "Exploit Prediction Scoring System"),
54+
("ssvc", "Stakeholder-Specific Vulnerability Categorization"),
55+
("openssl", "OpenSSL Severity"),
56+
],
57+
help_text="Identifier for the scoring system used. Available choices are: cvssv2: CVSSv2 Base Score,\ncvssv3: CVSSv3 Base Score,\ncvssv3.1: CVSSv3.1 Base Score,\ncvssv4: CVSSv4 Base Score,\nrhbs: RedHat Bugzilla severity,\nrhas: RedHat Aggregate severity,\narchlinux: Archlinux Vulnerability Group Severity,\ncvssv3.1_qr: CVSSv3.1 Qualitative Severity Rating,\ngeneric_textual: Generic textual severity rating,\napache_httpd: Apache Httpd Severity,\napache_tomcat: Apache Tomcat Severity,\nepss: Exploit Prediction Scoring System,\nssvc: Stakeholder-Specific Vulnerability Categorization,\nopenssl: OpenSSL Severity ",
58+
max_length=50,
59+
),
60+
),
61+
]
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import logging
11+
from pathlib import Path
12+
from traceback import format_exc as traceback_format_exc
13+
from typing import Iterable
14+
15+
from dateutil.parser import parse
16+
from fetchcode.vcs import fetch_via_vcs
17+
from packageurl import PackageURL
18+
from univers.version_range import OpensslVersionRange
19+
20+
from vulnerabilities import severity_systems
21+
from vulnerabilities.importer import AdvisoryData
22+
from vulnerabilities.importer import AffectedPackageV2
23+
from vulnerabilities.importer import PatchData
24+
from vulnerabilities.importer import VulnerabilitySeverity
25+
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2
26+
from vulnerabilities.pipes import openssl
27+
from vulnerabilities.utils import build_description
28+
from vulnerabilities.utils import create_weaknesses_list
29+
from vulnerabilities.utils import get_item
30+
from vulnerabilities.utils import load_json
31+
32+
33+
class OpenSSLImporterPipeline(VulnerableCodeBaseImporterPipelineV2):
34+
"""Import OpenSSL Advisories"""
35+
36+
pipeline_id = "openssl_importer_v2"
37+
spdx_license_expression = "Apache-2.0"
38+
importer_name = "OpenSSL Importer V2"
39+
40+
license_url = "https://github.com/openssl/openssl/blob/master/LICENSE.txt"
41+
repo_url = "git+https://github.com/openssl/release-metadata/"
42+
43+
@classmethod
44+
def steps(cls):
45+
return (
46+
cls.clone,
47+
cls.collect_and_store_advisories,
48+
cls.clean_downloads,
49+
)
50+
51+
def clone(self):
52+
self.log(f"Cloning `{self.repo_url}`")
53+
self.vcs_response = fetch_via_vcs(self.repo_url)
54+
self.advisory_path = Path(self.vcs_response.dest_dir)
55+
56+
def advisories_count(self):
57+
vuln_directory = self.advisory_path / "secjson"
58+
return sum(1 for _ in vuln_directory.glob("CVE-*.json"))
59+
60+
def collect_advisories(self) -> Iterable[AdvisoryData]:
61+
vuln_directory = self.advisory_path / "secjson"
62+
63+
for advisory in vuln_directory.glob("CVE-*.json"):
64+
yield self.to_advisory_data(advisory)
65+
66+
def to_advisory_data(self, file: Path) -> Iterable[AdvisoryData]:
67+
# TODO: Collect the advisory credits, see https://github.com/aboutcode-org/vulnerablecode/issues/2121
68+
69+
affected_packages = []
70+
severities = []
71+
references = []
72+
patches = []
73+
fix_commits = {}
74+
cwe_string = None
75+
76+
data = load_json(file)
77+
advisory_text = file.read_text()
78+
advisory = get_item(data, "containers", "cna")
79+
description = get_item(advisory, "descriptions", 0, "value")
80+
title = advisory.get("title")
81+
date_published = parse(get_item(advisory, "datePublic"))
82+
cve = get_item(data, "cveMetadata", "cveId")
83+
severity_score = get_item(advisory, "metrics", 0, "other", "content", "text")
84+
85+
for reference in get_item(advisory, "references") or []:
86+
ref_name = reference.get("name")
87+
ref_url = reference.get("url")
88+
if not ref_url:
89+
continue
90+
91+
tag = get_item(reference, "tags", 0) or ""
92+
tag = tag.lower()
93+
references.append(openssl.get_reference(ref_name, tag, ref_url))
94+
95+
if tag != "patch":
96+
continue
97+
98+
if not ref_name:
99+
patches.append(PatchData(patch_url=ref_url))
100+
continue
101+
102+
fix_commits[ref_name.split()[0]] = ref_url
103+
104+
for affected in get_item(advisory, "affected", 0, "versions") or []:
105+
if affected.get("status") != "affected":
106+
continue
107+
fixed_by_commit_patches = []
108+
affected_constraints = None
109+
fixed_version = None
110+
111+
try:
112+
affected_constraints, fixed_version = openssl.parse_affected_fixed(affected)
113+
except Exception as e:
114+
self.log(
115+
f"Failed to parse OpenSSL version for: {cve} with error {e!r}:\n{traceback_format_exc()}",
116+
level=logging.ERROR,
117+
)
118+
continue
119+
120+
fixed_version_range = (
121+
OpensslVersionRange.from_versions([fixed_version]) if fixed_version else None
122+
)
123+
124+
affected_version_range = (
125+
OpensslVersionRange(constraints=affected_constraints)
126+
if affected_constraints
127+
else None
128+
)
129+
130+
if fixed_version and (commit_url := fix_commits.get(fixed_version)):
131+
if patch := openssl.get_commit_patch(
132+
url=commit_url,
133+
logger=self.log,
134+
):
135+
fixed_by_commit_patches.append(patch)
136+
137+
affected_packages.append(
138+
AffectedPackageV2(
139+
package=PackageURL(type="openssl", name="openssl"),
140+
affected_version_range=affected_version_range,
141+
fixed_version_range=fixed_version_range,
142+
fixed_by_commit_patches=fixed_by_commit_patches,
143+
)
144+
)
145+
146+
if severity_score:
147+
severities.append(
148+
VulnerabilitySeverity(
149+
system=severity_systems.OPENSSL,
150+
value=severity_score,
151+
url=f"https://openssl-library.org/news/secjson/{cve.lower()}.json",
152+
)
153+
)
154+
155+
if "problemTypes" in advisory:
156+
problem_type = get_item(advisory, "problemTypes", 0, "descriptions", 0)
157+
cwe_string = problem_type.get("cweId")
158+
159+
weaknesses = create_weaknesses_list([cwe_string]) if cwe_string else []
160+
161+
return AdvisoryData(
162+
advisory_id=cve,
163+
aliases=[],
164+
summary=build_description(summary=title, description=description),
165+
date_published=date_published,
166+
affected_packages=affected_packages,
167+
references_v2=references,
168+
severities=severities,
169+
weaknesses=weaknesses,
170+
patches=patches,
171+
url=f"https://github.com/openssl/release-metadata/blob/main/secjson/{cve}.json",
172+
original_advisory_text=advisory_text,
173+
)
174+
175+
def clean_downloads(self):
176+
if self.vcs_response:
177+
self.log(f"Removing cloned repository")
178+
self.vcs_response.delete()
179+
180+
def on_failure(self):
181+
self.clean_downloads()

vulnerabilities/pipes/openssl.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
from urllib.parse import parse_qs
11+
from urllib.parse import urlparse
12+
13+
from univers.version_constraint import VersionConstraint
14+
from univers.versions import OpensslVersion
15+
16+
from vulnerabilities.importer import PackageCommitPatchData
17+
from vulnerabilities.importer import ReferenceV2
18+
from vulnerabilities.models import AdvisoryReference
19+
from vulnerabilities.utils import get_item
20+
21+
22+
def parse_affected_fixed(affected):
23+
impact_lower = affected.get("version")
24+
affected_constraint = []
25+
fixed_version = None
26+
27+
if not impact_lower:
28+
return affected_constraint, fixed_version
29+
30+
if impact_upper := affected.get("lessThan"):
31+
fixed_version = impact_upper
32+
affected_constraint.append(
33+
VersionConstraint(
34+
comparator="<",
35+
version=OpensslVersion(string=impact_upper),
36+
)
37+
)
38+
elif impact_upper := affected.get("lessThanOrEqual"):
39+
affected_constraint.append(
40+
VersionConstraint(
41+
comparator="<=",
42+
version=OpensslVersion(string=impact_upper),
43+
)
44+
)
45+
46+
lower_comp = "=" if not affected_constraint else ">="
47+
affected_constraint.append(
48+
VersionConstraint(
49+
comparator=lower_comp,
50+
version=OpensslVersion(string=impact_lower),
51+
)
52+
)
53+
54+
return affected_constraint, fixed_version
55+
56+
57+
def get_commit_patch(url, logger):
58+
"""Return PackageCommitPatchData from OpenSSL commit url."""
59+
60+
vcs_url = "https://github.com/openssl/openssl/"
61+
hash = None
62+
63+
if url.startswith("https://github.openssl.org/"):
64+
# unknow vcs url, these are instead stored as references.
65+
return
66+
67+
if url.startswith("https://github.com/"):
68+
vcs_url, hash = url.split("/commit/")
69+
elif url.startswith("https://git.openssl.org/"):
70+
parsed = urlparse(url)
71+
params = parse_qs(parsed.query, separator=";")
72+
if "h" in params:
73+
# git.openssl.org has moved to github.com/openssl/openssl
74+
hash = get_item(params, "h", 0)
75+
76+
if not hash:
77+
logger(f"Unsupported commit url {url}")
78+
return
79+
80+
return PackageCommitPatchData(
81+
vcs_url=vcs_url,
82+
commit_hash=hash[:40],
83+
)
84+
85+
86+
def get_reference(reference_name, tag, reference_url):
87+
name = reference_name.lower() if reference_name else ""
88+
89+
ref_type = (
90+
AdvisoryReference.COMMIT
91+
if "commit" in name or tag == "patch"
92+
else AdvisoryReference.ADVISORY
93+
if "advisory" in name
94+
else AdvisoryReference.OTHER
95+
)
96+
97+
return ReferenceV2(
98+
reference_id=reference_name or tag,
99+
reference_type=ref_type,
100+
url=reference_url,
101+
)

vulnerabilities/severity_systems.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
#
99

1010
import dataclasses
11-
from datetime import datetime
1211

1312
from cvss import CVSS2
1413
from cvss import CVSS3
@@ -185,6 +184,18 @@ def get(self, scoring_elements: str) -> dict:
185184
"Low",
186185
]
187186

187+
OPENSSL = ScoringSystem(
188+
identifier="openssl",
189+
name="OpenSSL Severity",
190+
url="https://openssl-library.org/policies/general/security-policy/",
191+
)
192+
OPENSSL.choices = [
193+
"Critical",
194+
"High",
195+
"Moderate",
196+
"Low",
197+
]
198+
188199

189200
@dataclasses.dataclass(order=True)
190201
class EPSSScoringSystem(ScoringSystem):
@@ -227,5 +238,6 @@ def get(self, scoring_elements: str):
227238
APACHE_TOMCAT,
228239
EPSS,
229240
SSVC,
241+
OPENSSL,
230242
)
231243
}

0 commit comments

Comments
 (0)