Skip to content

Commit 5216f64

Browse files
committed
Migrate Ruby importer to Advisory V2
Add a test Signed-off-by: ziad hany <ziadhany2016@gmail.com>
1 parent 2b0fba2 commit 5216f64

File tree

10 files changed

+369
-61
lines changed

10 files changed

+369
-61
lines changed

vulnerabilities/pipelines/v2_importers/ruby_importer.py

Lines changed: 75 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
from packageurl import PackageURL
1717
from pytz import UTC
1818
from univers.version_range import GemVersionRange
19+
from univers.version_range import InvalidVersionRange
1920

20-
from vulnerabilities.importer import AdvisoryData, AffectedPackageV2
21+
from vulnerabilities.importer import AdvisoryData
22+
from vulnerabilities.importer import AffectedPackageV2
2123
from vulnerabilities.importer import ReferenceV2
2224
from vulnerabilities.importer import VulnerabilitySeverity
2325
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2
@@ -68,24 +70,30 @@ def clone(self):
6870
self.vcs_response = fetch_via_vcs(self.repo_url)
6971

7072
def advisories_count(self):
71-
return 10
73+
base_path = Path(self.vcs_response.dest_dir)
74+
return sum(1 for _ in base_path.rglob("*.yml"))
7275

7376
def collect_advisories(self) -> Iterable[AdvisoryData]:
7477
base_path = Path(self.vcs_response.dest_dir)
75-
supported_subdir = ["rubies", "gems"]
76-
for subdir in supported_subdir:
77-
for file_path in base_path.glob(f"{subdir}/**/*.yml"):
78-
if file_path.name.startswith("OSVDB-"):
79-
continue
80-
81-
raw_data = load_yaml(file_path)
82-
advisory_id = file_path.stem
83-
advisory_url = get_advisory_url(
84-
file=file_path,
85-
base_path=base_path,
86-
url="https://github.com/rubysec/ruby-advisory-db/blob/master/",
87-
)
88-
yield parse_ruby_advisory(advisory_id, raw_data, subdir, advisory_url)
78+
for file_path in base_path.rglob("*.yml"):
79+
if file_path.name.startswith("OSVDB-"):
80+
continue
81+
82+
if "gems" in file_path.parts:
83+
subdir = "gems"
84+
elif "rubies" in file_path.parts:
85+
subdir = "rubies"
86+
else:
87+
continue
88+
89+
raw_data = load_yaml(file_path)
90+
advisory_id = file_path.stem
91+
advisory_url = get_advisory_url(
92+
file=file_path,
93+
base_path=base_path,
94+
url="https://github.com/rubysec/ruby-advisory-db/blob/master/",
95+
)
96+
yield parse_ruby_advisory(advisory_id, raw_data, subdir, advisory_url)
8997

9098
def clean_downloads(self):
9199
if self.vcs_response:
@@ -107,36 +115,37 @@ def parse_ruby_advisory(advisory_id, record, schema_type, advisory_url):
107115

108116
if not package_name:
109117
logger.error("Invalid package name")
110-
else:
111-
purl = PackageURL(type="gem", name=package_name)
112-
113-
return AdvisoryData(
114-
advisory_id=advisory_id,
115-
aliases=get_aliases(record),
116-
summary=get_summary(record),
117-
affected_packages=get_affected_packages(record, purl),
118-
references=get_references(record),
119-
severities=get_severities(record),
120-
date_published=get_publish_time(record),
121-
url=advisory_url,
122-
)
118+
return
119+
120+
purl = PackageURL(type="gem", name=package_name)
121+
return AdvisoryData(
122+
advisory_id=advisory_id,
123+
aliases=get_aliases(record),
124+
summary=get_summary(record),
125+
affected_packages=get_affected_packages(record, purl),
126+
references=get_references(record),
127+
severities=get_severities(record),
128+
date_published=get_publish_time(record),
129+
url=advisory_url,
130+
)
123131

124132
elif schema_type == "rubies":
125133
engine = record.get("engine") # engine enum: [jruby, rbx, ruby]
126134
if not engine:
127135
logger.error("Invalid engine name")
128-
else:
129-
purl = PackageURL(type="ruby", name=engine)
130-
return AdvisoryData(
131-
advisory_id=advisory_id,
132-
aliases=get_aliases(record),
133-
summary=get_summary(record),
134-
affected_packages=get_affected_packages(record, purl),
135-
severities=get_severities(record),
136-
references=get_references(record),
137-
date_published=get_publish_time(record),
138-
url=advisory_url,
139-
)
136+
return
137+
138+
purl = PackageURL(type="ruby", name=engine)
139+
return AdvisoryData(
140+
advisory_id=advisory_id,
141+
aliases=get_aliases(record),
142+
summary=get_summary(record),
143+
affected_packages=get_affected_packages(record, purl),
144+
severities=get_severities(record),
145+
references=get_references(record),
146+
date_published=get_publish_time(record),
147+
url=advisory_url,
148+
)
140149

141150

142151
def get_affected_packages(record, purl):
@@ -145,27 +154,32 @@ def get_affected_packages(record, purl):
145154
( patched_versions , unaffected_versions ) then passing the purl and the inverted safe_version_range
146155
to the AffectedPackage object
147156
"""
148-
safe_version_ranges = record.get("patched_versions", [])
149-
# this case happens when the advisory contain only 'patched_versions' field
150-
# and it has value None(i.e it is empty :( ).
151-
if not safe_version_ranges:
152-
safe_version_ranges = []
153-
safe_version_ranges += record.get("unaffected_versions", [])
154-
safe_version_ranges = [i for i in safe_version_ranges if i]
155-
156157
affected_packages = []
157-
affected_version_ranges = [
158-
GemVersionRange.from_native(elem).invert() for elem in safe_version_ranges
159-
]
160-
161-
for affected_version_range in affected_version_ranges:
162-
affected_packages.append(
163-
AffectedPackageV2(
164-
package=purl,
165-
affected_version_range=affected_version_range,
166-
fixed_version_range=None
158+
for unaffected_version in record.get("unaffected_versions", []):
159+
try:
160+
affected_version_range = GemVersionRange.from_native(unaffected_version).invert()
161+
affected_packages.append(
162+
AffectedPackageV2(
163+
package=purl,
164+
affected_version_range=affected_version_range,
165+
fixed_version_range=None,
166+
)
167167
)
168-
)
168+
except InvalidVersionRange as e:
169+
logger.error(f"InvalidVersionRange {e}")
170+
171+
for patched_version in record.get("patched_versions", []):
172+
try:
173+
fixed_version_range = GemVersionRange.from_native(patched_version)
174+
affected_packages.append(
175+
AffectedPackageV2(
176+
package=purl,
177+
affected_version_range=None,
178+
fixed_version_range=fixed_version_range,
179+
)
180+
)
181+
except InvalidVersionRange as e:
182+
logger.error(f"InvalidVersionRange {e}")
169183
return affected_packages
170184

171185

@@ -205,7 +219,7 @@ def get_severities(record):
205219

206220
cvss_v3 = record.get("cvss_v3")
207221
if cvss_v3:
208-
severities.append(VulnerabilitySeverity(system=CVSSV3, value=cvss_v4))
222+
severities.append(VulnerabilitySeverity(system=CVSSV3, value=cvss_v3))
209223

210224
cvss_v2 = record.get("cvss_v2")
211225
if cvss_v2:
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 pathlib import Path
11+
from unittest.mock import Mock
12+
from unittest.mock import patch
13+
14+
import pytest
15+
16+
from vulnerabilities.pipelines.v2_importers.ruby_importer import RubyImporterPipeline
17+
from vulnerabilities.tests import util_tests
18+
19+
TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "ruby-v2"
20+
21+
TEST_CVE_FILES = [
22+
TEST_DATA / "gems/CVE-2020-5257.yml",
23+
TEST_DATA / "gems/CVE-2024-6531.yml",
24+
TEST_DATA / "rubies/CVE-2011-2686.yml",
25+
TEST_DATA / "rubies/CVE-2022-25857.yml",
26+
]
27+
28+
29+
@pytest.mark.django_db
30+
@pytest.mark.parametrize("yml_file", TEST_CVE_FILES)
31+
def test_ruby_advisories_per_file(yml_file):
32+
pipeline = RubyImporterPipeline()
33+
pipeline.vcs_response = Mock(dest_dir=TEST_DATA)
34+
35+
with patch.object(Path, "rglob", return_value=[yml_file]):
36+
result = [adv.to_dict() for adv in pipeline.collect_advisories()]
37+
38+
expected_file = yml_file.with_name(yml_file.stem + "-expected.json")
39+
util_tests.check_results_against_json(result, expected_file)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[
2+
{
3+
"advisory_id": "CVE-2020-5257",
4+
"aliases": [
5+
"CVE-2020-5257",
6+
"GHSA-2p5p-m353-833w"
7+
],
8+
"summary": "Sort order SQL injection via `direction` parameter in administrate\nIn Administrate (rubygem) before version 0.13.0, when sorting by attributes\non a dashboard, the direction parameter was not validated before being\ninterpolated into the SQL query.\n\nThis could present a SQL injection if the attacker were able to modify the\ndirection parameter and bypass ActiveRecord SQL protections.\n\nWhilst this does have a high-impact, to exploit this you need access to the\nAdministrate dashboards, which should generally be behind authentication.",
9+
"affected_packages": [
10+
{
11+
"package": {
12+
"type": "gem",
13+
"namespace": "",
14+
"name": "administrate",
15+
"version": "",
16+
"qualifiers": "",
17+
"subpath": ""
18+
},
19+
"affected_version_range": null,
20+
"fixed_version_range": "vers:gem/>=0.13.0",
21+
"introduced_by_commit_patches": [],
22+
"fixed_by_commit_patches": []
23+
}
24+
],
25+
"references_v2": [],
26+
"patches": [],
27+
"severities": [
28+
{
29+
"system": "cvssv3",
30+
"value": "7.7",
31+
"scoring_elements": ""
32+
}
33+
],
34+
"date_published": "2020-03-14T00:00:00+00:00",
35+
"weaknesses": [],
36+
"url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/CVE-2020-5257.yml"
37+
}
38+
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
gem: administrate
3+
cve: 2020-5257
4+
ghsa: 2p5p-m353-833w
5+
title: Sort order SQL injection via `direction` parameter in administrate
6+
date: 2020-03-14
7+
url: https://github.com/advisories/GHSA-2p5p-m353-833w
8+
description: |
9+
In Administrate (rubygem) before version 0.13.0, when sorting by attributes
10+
on a dashboard, the direction parameter was not validated before being
11+
interpolated into the SQL query.
12+
13+
This could present a SQL injection if the attacker were able to modify the
14+
direction parameter and bypass ActiveRecord SQL protections.
15+
16+
Whilst this does have a high-impact, to exploit this you need access to the
17+
Administrate dashboards, which should generally be behind authentication.
18+
cvss_v3: 7.7
19+
patched_versions:
20+
- ">= 0.13.0"
21+
22+
related:
23+
url:
24+
- https://github.com/thoughtbot/administrate/commit/3ab838b83c5f565fba50e0c6f66fe4517f98eed3
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[
2+
{
3+
"advisory_id": "CVE-2024-6531",
4+
"aliases": [
5+
"CVE-2024-6531",
6+
"GHSA-vc8w-jr9v-vj7f"
7+
],
8+
"summary": "Bootstrap Cross-Site Scripting (XSS) vulnerability\nA vulnerability has been identified in Bootstrap that exposes users\nto Cross-Site Scripting (XSS) attacks. The issue is present in the\ncarousel component, where the data-slide and data-slide-to attributes\ncan be exploited through the href attribute of an <a> tag due to\ninadequate sanitization. This vulnerability could potentially enable\nattackers to execute arbitrary JavaScript within the victim's browser.",
9+
"affected_packages": [
10+
{
11+
"package": {
12+
"type": "gem",
13+
"namespace": "",
14+
"name": "bootstrap",
15+
"version": "",
16+
"qualifiers": "",
17+
"subpath": ""
18+
},
19+
"affected_version_range": "vers:gem/>=4.0.0",
20+
"fixed_version_range": null,
21+
"introduced_by_commit_patches": [],
22+
"fixed_by_commit_patches": []
23+
},
24+
{
25+
"package": {
26+
"type": "gem",
27+
"namespace": "",
28+
"name": "bootstrap",
29+
"version": "",
30+
"qualifiers": "",
31+
"subpath": ""
32+
},
33+
"affected_version_range": null,
34+
"fixed_version_range": "vers:gem/>4.6.2",
35+
"introduced_by_commit_patches": [],
36+
"fixed_by_commit_patches": []
37+
}
38+
],
39+
"references_v2": [],
40+
"patches": [],
41+
"severities": [
42+
{
43+
"system": "cvssv3",
44+
"value": "6.4",
45+
"scoring_elements": ""
46+
}
47+
],
48+
"date_published": "2024-07-11T00:00:00+00:00",
49+
"weaknesses": [],
50+
"url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/CVE-2024-6531.yml"
51+
}
52+
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
gem: bootstrap
3+
cve: 2024-6531
4+
ghsa: vc8w-jr9v-vj7f
5+
url: https://github.com/advisories/GHSA-vc8w-jr9v-vj7f
6+
title: Bootstrap Cross-Site Scripting (XSS) vulnerability
7+
date: 2024-07-11
8+
description: |
9+
A vulnerability has been identified in Bootstrap that exposes users
10+
to Cross-Site Scripting (XSS) attacks. The issue is present in the
11+
carousel component, where the data-slide and data-slide-to attributes
12+
can be exploited through the href attribute of an <a> tag due to
13+
inadequate sanitization. This vulnerability could potentially enable
14+
attackers to execute arbitrary JavaScript within the victim's browser.
15+
cvss_v3: 6.4
16+
unaffected_versions:
17+
- "< 4.0.0"
18+
patched_versions:
19+
- "> 4.6.2"
20+
related:
21+
url:
22+
- https://nvd.nist.gov/vuln/detail/CVE-2024-6531
23+
- https://www.herodevs.com/vulnerability-directory/cve-2024-6531
24+
- https://github.com/advisories/GHSA-vc8w-jr9v-vj7f

0 commit comments

Comments
 (0)