Skip to content

Commit cbf411e

Browse files
authored
Merge branch 'main' into example
2 parents a831e85 + 57c3cc9 commit cbf411e

8 files changed

Lines changed: 697 additions & 613 deletions

File tree

AUTHORS.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ The following organizations or individuals have contributed to this repo:
1313
- Navonil Das @NavonilDas
1414
- Tushar Upadhyay @tushar912
1515
- Hritik Vijay @hritik14
16-
- Tushar Goel @TG1999
16+
- Tushar Goel @TG1999
17+
- Keshav Priyadarshi @keshav-space

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
from vulnerabilities.importers import github
2424
from vulnerabilities.importers import nginx
2525
from vulnerabilities.importers import nvd
26+
from vulnerabilities.importers import openssl
2627

2728
IMPORTERS_REGISTRY = [
2829
nginx.NginxImporter,
2930
alpine_linux.AlpineImporter,
3031
github.GitHubAPIImporter,
3132
nvd.NVDImporter,
33+
openssl.OpensslImporter,
3234
]
3335

3436
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}

vulnerabilities/importers/openssl.py

Lines changed: 135 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -20,101 +20,152 @@
2020
# VulnerableCode is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222

23-
import dataclasses
24-
import re
25-
import xml.etree.ElementTree as ET
26-
from typing import Set
23+
import logging
24+
from datetime import timezone
25+
from typing import Iterable
26+
from urllib.parse import urljoin
2727

28+
import defusedxml.ElementTree as DET
2829
import requests
30+
from dateutil import parser as dateparser
2931
from packageurl import PackageURL
32+
from univers.version_range import OpensslVersionRange
33+
from univers.versions import OpensslVersion
3034

31-
from vulnerabilities.helpers import create_etag
32-
from vulnerabilities.helpers import nearest_patched_package
33-
from vulnerabilities.importer import Advisory
35+
from vulnerabilities.importer import AdvisoryData
36+
from vulnerabilities.importer import AffectedPackage
3437
from vulnerabilities.importer import Importer
3538
from vulnerabilities.importer import Reference
39+
from vulnerabilities.importer import VulnerabilitySeverity
40+
from vulnerabilities.severity_systems import SCORING_SYSTEMS
3641

42+
logger = logging.getLogger(__name__)
3743

38-
class OpenSSLImporter(Importer):
3944

45+
class OpensslImporter(Importer):
46+
spdx_license_expression = "Apache-2.0"
47+
license_url = "https://github.com/openssl/openssl/blob/master/LICENSE.txt"
4048
url = "https://www.openssl.org/news/vulnerabilities.xml"
4149

42-
def updated_advisories(self) -> Set[Advisory]:
43-
# Etags are like hashes of web responses. We maintain
44-
# (url, etag) mappings in the DB. `create_etag` creates
45-
# (url, etag) pair. If a (url, etag) already exists then the code
46-
# skips processing the response further to avoid duplicate work
47-
if create_etag(data_src=self, url=self.url, etag_key="ETag"):
48-
raw_data = self.fetch()
49-
advisories = self.to_advisories(raw_data)
50-
return self.batch_advisories(advisories)
51-
52-
return []
53-
5450
def fetch(self):
55-
return requests.get(self.url).content
56-
57-
@staticmethod
58-
def to_advisories(xml_response: str) -> Set[Advisory]:
59-
advisories = []
60-
pkg_name = "openssl"
61-
pkg_type = "generic"
62-
root = ET.fromstring(xml_response)
63-
for element in root:
64-
if element.tag == "issue":
65-
cve_id = ""
66-
summary = ""
67-
safe_pkg_versions = []
68-
vuln_pkg_versions = []
69-
ref_urls = []
70-
for info in element:
71-
72-
if info.tag == "cve":
73-
if info.attrib.get("name"):
74-
cve_id = "CVE-" + info.attrib.get("name")
75-
76-
else:
77-
continue
78-
79-
if cve_id == "CVE-2007-5502":
80-
# This CVE has weird version "fips-1.1.2".This is
81-
# probably a submodule. Skip this for now.
82-
continue
83-
84-
if info.tag == "affects":
85-
# Vulnerable package versions
86-
vuln_pkg_versions.append(info.attrib.get("version"))
87-
88-
if info.tag == "fixed":
89-
# Fixed package versions
90-
safe_pkg_versions.append(info.attrib.get("version"))
91-
92-
if info:
93-
commit_hash = info[0].attrib["hash"]
94-
ref_urls.append(
95-
Reference(
96-
url="https://github.com/openssl/openssl/commit/" + commit_hash
97-
)
98-
)
99-
if info.tag == "description":
100-
# Description
101-
summary = re.sub(r"\s+", " ", info.text).strip()
102-
103-
safe_purls = [
104-
PackageURL(name=pkg_name, type=pkg_type, version=version)
105-
for version in safe_pkg_versions
106-
]
107-
vuln_purls = [
108-
PackageURL(name=pkg_name, type=pkg_type, version=version)
109-
for version in vuln_pkg_versions
110-
]
111-
112-
advisory = Advisory(
113-
vulnerability_id=cve_id,
114-
summary=summary,
115-
affected_packages=nearest_patched_package(vuln_purls, safe_purls),
116-
references=ref_urls,
51+
response = requests.get(url=self.url)
52+
if not response.status_code == 200:
53+
logger.error(f"Error while fetching {self.url}: {response.status_code}")
54+
return
55+
return response.content
56+
57+
def advisory_data(self) -> Iterable[AdvisoryData]:
58+
xml_response = self.fetch()
59+
return parse_vulnerabilities(xml_response)
60+
61+
62+
def parse_vulnerabilities(xml_response) -> Iterable[AdvisoryData]:
63+
root = DET.fromstring(xml_response)
64+
for xml_issue in root:
65+
if xml_issue.tag == "issue":
66+
advisory = to_advisory_data(xml_issue)
67+
if advisory:
68+
yield advisory
69+
70+
71+
def to_advisory_data(xml_issue) -> AdvisoryData:
72+
"""
73+
Return AdvisoryData from given xml_issue
74+
"""
75+
76+
purl = PackageURL(type="openssl", name="openssl")
77+
cve = advisory_url = severity = summary = None
78+
safe_pkg_versions = {}
79+
vuln_pkg_versions_by_base_version = {}
80+
aliases = []
81+
references = []
82+
affected_packages = []
83+
date_published = xml_issue.attrib["public"].strip()
84+
85+
for info in xml_issue:
86+
if info.tag == "impact":
87+
severity = VulnerabilitySeverity(
88+
system=SCORING_SYSTEMS["generic_textual"], value=info.attrib["severity"]
89+
)
90+
91+
elif info.tag == "advisory":
92+
advisory_url = info.attrib["url"]
93+
if not advisory_url.startswith("https://web.archive.org"):
94+
advisory_url = urljoin("https://www.openssl.org", advisory_url)
95+
96+
elif info.tag == "cve":
97+
cve = info.attrib.get("name")
98+
# use made up alias to compensate for case when advisory doesn't have CVE-ID
99+
madeup_alias = f"VC-OPENSSL-{date_published}"
100+
if cve:
101+
cve = f"CVE-{cve}"
102+
madeup_alias = f"{madeup_alias}-{cve}"
103+
aliases.append(cve)
104+
references.append(Reference(reference_id=cve))
105+
aliases.append(madeup_alias)
106+
107+
elif info.tag == "affects":
108+
affected_base = info.attrib["base"]
109+
affected_version = info.attrib["version"]
110+
if affected_base.startswith("fips"):
111+
logger.error(
112+
f"{affected_base!r} is a OpenSSL-FIPS Object Module and isn't supported by OpensslImporter. Use a different importer."
113+
)
114+
return
115+
if affected_base in vuln_pkg_versions_by_base_version:
116+
vuln_pkg_versions_by_base_version[affected_base].append(affected_version)
117+
else:
118+
vuln_pkg_versions_by_base_version[affected_base] = [affected_version]
119+
120+
elif info.tag == "fixed":
121+
fixed_base = info.attrib["base"]
122+
fixed_version = info.attrib["version"]
123+
safe_pkg_versions[fixed_base] = fixed_version
124+
for commit in info:
125+
commit_hash = commit.attrib["hash"]
126+
references.append(
127+
Reference(
128+
url=urljoin("https://github.com/openssl/openssl/commit/", commit_hash)
129+
)
117130
)
118-
advisories.append(advisory)
119131

120-
return advisories
132+
elif info.tag == "description":
133+
summary = " ".join(info.text.split())
134+
135+
elif info.tag in ("reported", "problemtype", "title"):
136+
# as of now, these info isn't useful for AdvisoryData
137+
# for more see: https://github.com/nexB/vulnerablecode/issues/688
138+
continue
139+
else:
140+
logger.error(
141+
f"{info.tag!r} is a newly introduced tag. Modify the importer to make use of this new info."
142+
)
143+
144+
for base_version, affected_versions in vuln_pkg_versions_by_base_version.items():
145+
affected_version_range = OpensslVersionRange.from_versions(affected_versions)
146+
fixed_version = None
147+
if base_version in safe_pkg_versions:
148+
fixed_version = OpensslVersion(safe_pkg_versions[base_version])
149+
affected_package = AffectedPackage(
150+
package=purl,
151+
affected_version_range=affected_version_range,
152+
fixed_version=fixed_version,
153+
)
154+
affected_packages.append(affected_package)
155+
156+
if severity and advisory_url:
157+
references.append(Reference(url=advisory_url, severities=[severity]))
158+
elif advisory_url:
159+
references.append(Reference(url=advisory_url))
160+
161+
parsed_date_published = dateparser.parse(date_published, yearfirst=True).replace(
162+
tzinfo=timezone.utc
163+
)
164+
165+
return AdvisoryData(
166+
aliases=aliases,
167+
summary=summary,
168+
affected_packages=affected_packages,
169+
references=references,
170+
date_published=parsed_date_published,
171+
)

vulnerabilities/tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ def no_rmtree(monkeypatch):
4848
"test_npm.py",
4949
"test_apache_kafka.py",
5050
"test_apache_tomcat.py",
51-
"test_openssl.py",
5251
"test_api.py",
5352
"test_package_managers.py",
5453
"test_archlinux.py",

0 commit comments

Comments
 (0)