Skip to content

Commit 2eedabc

Browse files
committed
Add a notice and the spdx_license_expression
Resolve merge conflict Add a docstring to get_affected_packages Add a unite test for get_affected_packages function Remove unused variables Fix sorted affected_package_merge Add ruby importer and improver Fix style test Fix test Rewrite affected_packages Ruby initial config Reference: #796 Clean imported data after import process Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> Fix sorted affected_package_merge Refactor Ruby importer and improver Add ruby importer and improver Fix style test Fix test Rewrite affected_packages Ruby initial config Reference: #796 Signed-off-by: ziadhany <ziadhany2016@gmail.com>
1 parent b27c485 commit 2eedabc

19 files changed

Lines changed: 715 additions & 255 deletions

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from vulnerabilities.importers import pysec
3333
from vulnerabilities.importers import redhat
3434
from vulnerabilities.importers import retiredotnet
35+
from vulnerabilities.importers import ruby
3536
from vulnerabilities.importers import suse_scores
3637
from vulnerabilities.importers import ubuntu
3738
from vulnerabilities.importers import ubuntu_usn
@@ -67,6 +68,7 @@
6768
fireeye.FireyeImporter,
6869
apache_kafka.ApacheKafkaImporter,
6970
oss_fuzz.OSSFuzzImporter,
71+
ruby.RubyImporter,
7072
]
7173

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

vulnerabilities/importers/ruby.py

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

10-
import asyncio
11-
from typing import List
12-
from typing import Set
10+
import logging
11+
from pathlib import Path
12+
from typing import Iterable
1313

1414
from dateutil.parser import parse
1515
from packageurl import PackageURL
1616
from pytz import UTC
17-
from univers.version_range import VersionRange
18-
from univers.versions import SemverVersion
17+
from univers.version_range import GemVersionRange
1918

2019
from vulnerabilities.importer import AdvisoryData
20+
from vulnerabilities.importer import AffectedPackage
2121
from vulnerabilities.importer import Importer
2222
from vulnerabilities.importer import Reference
23-
from vulnerabilities.package_managers import RubyVersionAPI
23+
from vulnerabilities.importer import VulnerabilitySeverity
24+
from vulnerabilities.severity_systems import SCORING_SYSTEMS
25+
from vulnerabilities.utils import build_description
2426
from vulnerabilities.utils import load_yaml
25-
from vulnerabilities.utils import nearest_patched_package
2627

28+
logger = logging.getLogger(__name__)
2729

28-
class RubyImporter(Importer):
29-
def __enter__(self):
30-
super(RubyImporter, self).__enter__()
31-
32-
if not getattr(self, "_added_files", None):
33-
self._added_files, self._updated_files = self.file_changes(
34-
recursive=True, file_ext="yml", subdir="./gems"
35-
)
3630

37-
self.pkg_manager_api = RubyVersionAPI()
38-
self.set_api(self.collect_packages())
39-
40-
def set_api(self, packages):
41-
asyncio.run(self.pkg_manager_api.load_api(packages))
42-
43-
def updated_advisories(self) -> Set[AdvisoryData]:
44-
files = self._updated_files.union(self._added_files)
45-
advisories = []
46-
for f in files:
47-
processed_data = self.process_file(f)
48-
if processed_data:
49-
advisories.append(processed_data)
50-
return self.batch_advisories(advisories)
51-
52-
def collect_packages(self):
53-
packages = set()
54-
files = self._updated_files.union(self._added_files)
55-
for f in files:
56-
data = load_yaml(f)
57-
if data.get("gem"):
58-
packages.add(data["gem"])
59-
60-
return packages
61-
62-
def process_file(self, path) -> List[AdvisoryData]:
63-
record = load_yaml(path)
31+
class RubyImporter(Importer):
32+
license_url = "https://github.com/rubysec/ruby-advisory-db/blob/master/LICENSE.txt"
33+
repo_url = "git+https://github.com/rubysec/ruby-advisory-db"
34+
spdx_license_expression = "LicenseRef-scancode-public-domain-disclaimer"
35+
notice = """
36+
If you submit code or data to the ruby-advisory-db that is copyrighted by
37+
yourself, upon submission you hereby agree to release it into the public
38+
domain.
39+
40+
The data imported from the ruby-advisory-db have been filtered to exclude
41+
any non-public domain data from the data copyrighted by the Open
42+
Source Vulnerability Database (http://osvdb.org).
43+
44+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
50+
SOFTWARE.
51+
"""
52+
53+
def advisory_data(self) -> Iterable[AdvisoryData]:
54+
try:
55+
self.clone(self.repo_url)
56+
base_path = Path(self.vcs_response.dest_dir)
57+
supported_subdir = ["rubies", "gems"]
58+
for subdir in supported_subdir:
59+
for file_path in base_path.glob(f"{subdir}/**/*.yml"):
60+
if file_path.name.startswith("OSVDB-"):
61+
continue
62+
raw_data = load_yaml(file_path)
63+
yield parse_ruby_advisory(raw_data, subdir)
64+
finally:
65+
if self.vcs_response:
66+
self.vcs_response.delete()
67+
68+
69+
def parse_ruby_advisory(record, schema_type):
70+
"""
71+
Parse a ruby advisory file and return an AdvisoryData or None.
72+
Each advisory file contains the advisory information in YAML format.
73+
Schema: https://github.com/rubysec/ruby-advisory-db/tree/master/spec/schemas
74+
"""
75+
if schema_type == "gems":
6476
package_name = record.get("gem")
65-
if not package_name:
66-
return
6777

68-
if "cve" in record:
69-
cve_id = "CVE-{}".format(record["cve"])
78+
if not package_name:
79+
logger.error("Invalid package name")
7080
else:
71-
return
72-
73-
publish_time = parse(record["date"]).replace(tzinfo=UTC)
74-
safe_version_ranges = record.get("patched_versions", [])
75-
# this case happens when the advisory contain only 'patched_versions' field
76-
# and it has value None(i.e it is empty :( ).
77-
if not safe_version_ranges:
78-
safe_version_ranges = []
79-
safe_version_ranges += record.get("unaffected_versions", [])
80-
safe_version_ranges = [i for i in safe_version_ranges if i]
81-
82-
if not getattr(self, "pkg_manager_api", None):
83-
self.pkg_manager_api = RubyVersionAPI()
84-
all_vers = self.pkg_manager_api.get(package_name, until=publish_time).valid_versions
85-
safe_versions, affected_versions = self.categorize_versions(all_vers, safe_version_ranges)
86-
87-
impacted_purls = [
88-
PackageURL(
89-
name=package_name,
90-
type="gem",
91-
version=version,
81+
purl = PackageURL(type="gem", name=package_name)
82+
83+
return AdvisoryData(
84+
aliases=get_aliases(record),
85+
summary=get_summary(record),
86+
affected_packages=get_affected_packages(record, purl),
87+
references=get_references(record),
88+
date_published=get_publish_time(record),
9289
)
93-
for version in affected_versions
94-
]
95-
96-
resolved_purls = [
97-
PackageURL(
98-
name=package_name,
99-
type="gem",
100-
version=version,
90+
91+
elif schema_type == "rubies":
92+
engine = record.get("engine") # engine enum: [jruby, rbx, ruby]
93+
if not engine:
94+
logger.error("Invalid engine name")
95+
else:
96+
purl = PackageURL(type="ruby", name=engine)
97+
return AdvisoryData(
98+
aliases=get_aliases(record),
99+
summary=get_summary(record),
100+
affected_packages=get_affected_packages(record, purl),
101+
references=get_references(record),
102+
date_published=get_publish_time(record),
101103
)
102-
for version in safe_versions
103-
]
104104

105-
references = []
106-
if record.get("url"):
107-
references.append(Reference(url=record.get("url")))
108105

109-
return AdvisoryData(
110-
summary=record.get("description", ""),
111-
affected_packages=nearest_patched_package(impacted_purls, resolved_purls),
112-
references=references,
113-
vulnerability_id=cve_id,
106+
def get_affected_packages(record, purl):
107+
"""
108+
Return AffectedPackage objects one for each affected_version_range and invert the safe_version_ranges
109+
( patched_versions , unaffected_versions ) then passing the purl and the inverted safe_version_range
110+
to the AffectedPackage object
111+
"""
112+
safe_version_ranges = record.get("patched_versions", [])
113+
# this case happens when the advisory contain only 'patched_versions' field
114+
# and it has value None(i.e it is empty :( ).
115+
if not safe_version_ranges:
116+
safe_version_ranges = []
117+
safe_version_ranges += record.get("unaffected_versions", [])
118+
safe_version_ranges = [i for i in safe_version_ranges if i]
119+
120+
affected_packages = []
121+
affected_version_ranges = [
122+
GemVersionRange.from_native(elem).invert() for elem in safe_version_ranges
123+
]
124+
125+
for affected_version_range in affected_version_ranges:
126+
affected_packages.append(
127+
AffectedPackage(
128+
package=purl,
129+
affected_version_range=affected_version_range,
130+
)
114131
)
132+
return affected_packages
133+
134+
135+
def get_aliases(record) -> [str]:
136+
aliases = []
137+
if record.get("cve"):
138+
aliases.append("CVE-{}".format(record.get("cve")))
139+
if record.get("osvdb"):
140+
aliases.append("OSV-{}".format(record.get("osvdb")))
141+
if record.get("ghsa"):
142+
aliases.append("GHSA-{}".format(record.get("ghsa")))
143+
return aliases
115144

116-
@staticmethod
117-
def categorize_versions(all_versions, unaffected_version_ranges):
118145

119-
for id, elem in enumerate(unaffected_version_ranges):
120-
unaffected_version_ranges[id] = VersionRange.from_scheme_version_spec_string(
121-
"semver", elem
146+
def get_references(record) -> [Reference]:
147+
references = []
148+
cvss_v2 = record.get("cvss_v2")
149+
cvss_v3 = record.get("cvss_v3")
150+
151+
if record.get("url"):
152+
if not (cvss_v2 or cvss_v3):
153+
references.append(Reference(url=record.get("url")))
154+
if cvss_v2:
155+
references.append(
156+
Reference(
157+
url=record.get("url"),
158+
severities=[
159+
VulnerabilitySeverity(system=SCORING_SYSTEMS["cvssv2"], value=cvss_v2)
160+
],
161+
)
122162
)
163+
if cvss_v3:
164+
references.append(
165+
Reference(
166+
url=record.get("url"),
167+
severities=[
168+
VulnerabilitySeverity(system=SCORING_SYSTEMS["cvssv3"], value=cvss_v3)
169+
],
170+
)
171+
)
172+
return references
173+
174+
175+
def get_publish_time(record):
176+
date = record.get("date")
177+
if not date:
178+
return
179+
return parse(date).replace(tzinfo=UTC)
180+
123181

124-
safe_versions = []
125-
vulnerable_versions = []
126-
for i in all_versions:
127-
vobj = SemverVersion(i)
128-
is_vulnerable = False
129-
for ver_rng in unaffected_version_ranges:
130-
if vobj in ver_rng:
131-
safe_versions.append(i)
132-
is_vulnerable = True
133-
break
134-
135-
if not is_vulnerable:
136-
vulnerable_versions.append(i)
137-
138-
return safe_versions, vulnerable_versions
182+
def get_summary(record):
183+
title = record.get("title") or ""
184+
description = record.get("description") or ""
185+
return build_description(summary=title, description=description)

vulnerabilities/improvers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
valid_versions.DebianOvalImprover,
2626
valid_versions.UbuntuOvalImprover,
2727
valid_versions.OSSFuzzImprover,
28+
valid_versions.RubyImprover,
2829
# vulnerability_status.VulnerabilityStatusImprover,
2930
]
3031

vulnerabilities/improvers/valid_versions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from vulnerabilities.importers.nginx import NginxImporter
3737
from vulnerabilities.importers.npm import NpmImporter
3838
from vulnerabilities.importers.oss_fuzz import OSSFuzzImporter
39+
from vulnerabilities.importers.ruby import RubyImporter
3940
from vulnerabilities.importers.ubuntu import UbuntuImporter
4041
from vulnerabilities.improver import MAX_CONFIDENCE
4142
from vulnerabilities.improver import Improver
@@ -483,3 +484,8 @@ class UbuntuOvalImprover(ValidVersionImprover):
483484
class OSSFuzzImprover(ValidVersionImprover):
484485
importer = OSSFuzzImporter
485486
ignorable_versions = []
487+
488+
489+
class RubyImprover(ValidVersionImprover):
490+
importer = RubyImporter
491+
ignorable_versions = []

vulnerabilities/tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ def no_rmtree(monkeypatch):
2626
# Step 3: Migrate all the tests
2727
collect_ignore = [
2828
"test_package_managers.py",
29-
"test_ruby.py",
3029
"test_rust.py",
3130
"test_suse_backports.py",
3231
"test_suse.py",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"aliases": [
3+
"CVE-2007-5770"
4+
],
5+
"summary": "Ruby Net::HTTPS library does not validate server certificate CN\nThe (1) Net::ftptls, (2) Net::telnets, (3) Net::imap, (4) Net::pop, and (5)\nNet::smtp libraries in Ruby 1.8.5 and 1.8.6 do not verify that the\ncommonName (CN) field in a server certificate matches the domain name in a\nrequest sent over SSL, which makes it easier for remote attackers to\nintercept SSL transmissions via a man-in-the-middle attack or spoofed web\nsite, different components than CVE-2007-5162.",
6+
"affected_packages": [
7+
{
8+
"package": {
9+
"type": "ruby",
10+
"namespace": null,
11+
"name": "ruby",
12+
"version": null,
13+
"qualifiers": null,
14+
"subpath": null
15+
},
16+
"affected_version_range": "vers:gem/<1.8.6.230|>=1.8.7",
17+
"fixed_version": null
18+
},
19+
{
20+
"package": {
21+
"type": "ruby",
22+
"namespace": null,
23+
"name": "ruby",
24+
"version": null,
25+
"qualifiers": null,
26+
"subpath": null
27+
},
28+
"affected_version_range": "vers:gem/<1.8.7",
29+
"fixed_version": null
30+
}
31+
],
32+
"references": [
33+
{
34+
"reference_id": "",
35+
"url": "http://www.cvedetails.com/cve/CVE-2007-5770/",
36+
"severities": [
37+
{
38+
"system": "cvssv2",
39+
"value": "4.3",
40+
"scoring_elements": ""
41+
}
42+
]
43+
}
44+
],
45+
"date_published": "2007-10-08T00:00:00+00:00",
46+
"weaknesses": []
47+
}

0 commit comments

Comments
 (0)