Skip to content

Commit ddd8139

Browse files
committed
Add v3 pipeline collect OpenSSL advisory
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent 6255cb2 commit ddd8139

File tree

9 files changed

+528
-11
lines changed

9 files changed

+528
-11
lines changed
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: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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+
affected_packages = []
68+
severities = []
69+
references = []
70+
patches = []
71+
fix_commits = {}
72+
cwe_string = None
73+
74+
data = load_json(file)
75+
advisory_text = file.read_text()
76+
advisory = get_item(data, "containers", "cna")
77+
description = get_item(advisory, "descriptions", 0, "value")
78+
title = advisory.get("title")
79+
date_published = parse(get_item(advisory, "datePublic"))
80+
cve = get_item(data, "cveMetadata", "cveId")
81+
severity_score = get_item(advisory, "metrics", 0, "other", "content", "text")
82+
83+
for reference in get_item(advisory, "references") or []:
84+
ref_name = reference.get("name")
85+
ref_url = reference.get("url")
86+
if not ref_url:
87+
continue
88+
89+
tag = get_item(reference, "tags", 0) or ""
90+
tag = tag.lower()
91+
references.append(openssl.get_reference(ref_name, tag, ref_url))
92+
93+
if tag != "patch":
94+
continue
95+
96+
if not ref_name:
97+
patches.append(PatchData(patch_url=ref_url))
98+
continue
99+
100+
fix_commits[ref_name.split()[0]] = ref_url
101+
102+
for affected in get_item(advisory, "affected", 0, "versions") or []:
103+
if affected.get("status") != "affected":
104+
continue
105+
fixed_by_commit_patches = []
106+
affected_constraints = None
107+
fixed_version = None
108+
109+
try:
110+
affected_constraints, fixed_version = openssl.parse_affected_fixed(affected)
111+
except Exception as e:
112+
self.log(
113+
f"Failed to parse OpenSSL version for: {cve} with error {e!r}:\n{traceback_format_exc()}",
114+
level=logging.ERROR,
115+
)
116+
continue
117+
118+
fixed_version_range = (
119+
OpensslVersionRange.from_versions([fixed_version]) if fixed_version else None
120+
)
121+
122+
affected_version_range = (
123+
OpensslVersionRange(constraints=affected_constraints)
124+
if affected_constraints
125+
else None
126+
)
127+
128+
if fixed_version and (commit_url := fix_commits.get(fixed_version)):
129+
if patch := openssl.get_commit_patch(
130+
url=commit_url,
131+
logger=self.log,
132+
):
133+
fixed_by_commit_patches.append(patch)
134+
135+
affected_packages.append(
136+
AffectedPackageV2(
137+
package=PackageURL(type="openssl", name="openssl"),
138+
affected_version_range=affected_version_range,
139+
fixed_version_range=fixed_version_range,
140+
fixed_by_commit_patches=fixed_by_commit_patches,
141+
)
142+
)
143+
144+
if severity_score:
145+
severities.append(
146+
VulnerabilitySeverity(
147+
system=severity_systems.OPENSSL,
148+
value=severity_score,
149+
url=f"https://openssl-library.org/news/secjson/{cve.lower()}.json",
150+
)
151+
)
152+
153+
if "problemTypes" in advisory:
154+
problem_type = get_item(advisory, "problemTypes", 0, "descriptions", 0)
155+
cwe_string = problem_type.get("cweId")
156+
157+
weaknesses = create_weaknesses_list([cwe_string]) if cwe_string else []
158+
159+
return AdvisoryData(
160+
advisory_id=cve,
161+
aliases=[],
162+
summary=build_description(summary=title, description=description),
163+
date_published=date_published,
164+
affected_packages=affected_packages,
165+
references_v2=references,
166+
severities=severities,
167+
weaknesses=weaknesses,
168+
patches=patches,
169+
url=f"https://github.com/openssl/release-metadata/blob/main/secjson/{cve}.json",
170+
original_advisory_text=advisory_text,
171+
)
172+
173+
def clean_downloads(self):
174+
if self.vcs_response:
175+
self.log(f"Removing cloned repository")
176+
self.vcs_response.delete()
177+
178+
def on_failure(self):
179+
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)