Skip to content

Commit 81dd565

Browse files
committed
Add test for ruby importer and modularise the the ruby importer
Signed-off-by: Shivam Sandbhor <shivam.sandbhor@gmail.com>
1 parent 70ba5de commit 81dd565

7 files changed

Lines changed: 214 additions & 23 deletions

File tree

vulnerabilities/importers/ruby.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ def __enter__(self):
2121
self._added_files, self._updated_files = self.file_changes(
2222
recursive=True, file_ext='yml', subdir='./gems')
2323

24-
self.pkg_manager_api = rubyAPI()
25-
2624
def updated_advisories(self) -> Set[Advisory]:
2725
files = self._updated_files.union(self._added_files)
2826
advisories = []
@@ -32,8 +30,6 @@ def updated_advisories(self) -> Set[Advisory]:
3230
return self.batch_advisories(advisories)
3331

3432
def _process_file(self, path) -> List[Advisory]:
35-
advisories = []
36-
3733
with open(path) as f:
3834
record = yaml.safe_load(f)
3935
package_name = record.get(
@@ -47,24 +43,20 @@ def _process_file(self, path) -> List[Advisory]:
4743
else:
4844
return
4945

50-
patched_version_ranges = record.get('patched_versions', [])
51-
# this case happens when the advisory contain 'patched_versions' field
52-
# and it has value None(i.e it is empty :( )
53-
if not patched_version_ranges:
54-
return
55-
affected_versions = self.pkg_manager_api.get_all_version_of_package(
46+
safe_version_ranges = record.get('patched_versions', [])
47+
# this case happens when the advisory contain only 'patched_versions' field
48+
# and it has value None(i.e it is empty :( ).
49+
if not safe_version_ranges:
50+
safe_version_ranges = []
51+
safe_version_ranges += record.get('unaffected_versions', [])
52+
safe_version_ranges = [i for i in safe_version_ranges if i]
53+
54+
if not getattr(self, 'pkg_manager_api', None):
55+
self.pkg_manager_api = rubyAPI()
56+
all_vers = self.pkg_manager_api.get_all_version_of_package(
5657
package_name)
57-
patched_versions = set()
58-
for version_range in patched_version_ranges:
59-
try:
60-
spec = RangeSpecifier(version_range)
61-
patched_versions.update(
62-
set(filter(lambda x: x in spec, affected_versions)))
63-
affected_versions -= patched_versions
64-
if not affected_versions:
65-
break
66-
except InvalidSpecifier:
67-
continue
58+
safe_versions, affected_versions = self.categorize_versions(
59+
all_vers, safe_version_ranges)
6860

6961
impacted_purls = {
7062
PackageURL(
@@ -78,7 +70,7 @@ def _process_file(self, path) -> List[Advisory]:
7870
name=package_name,
7971
type='gem',
8072
version=version,
81-
) for version in patched_versions}
73+
) for version in safe_versions}
8274

8375
return Advisory(
8476
summary=record.get('description', ''),
@@ -87,7 +79,26 @@ def _process_file(self, path) -> List[Advisory]:
8779
reference_urls=record.get('url', ''),
8880
cve_id=cve_id
8981
)
90-
return advisories
82+
83+
@staticmethod
84+
def categorize_versions(all_versions, unaffected_version_ranges):
85+
86+
for id, elem in enumerate(unaffected_version_ranges):
87+
try:
88+
unaffected_version_ranges[id] = RangeSpecifier(
89+
elem.replace(' ', ''))
90+
except InvalidSpecifier:
91+
continue
92+
93+
safe_versions = set()
94+
for i in all_versions:
95+
for ver_rng in unaffected_version_ranges:
96+
97+
if i in ver_rng:
98+
99+
safe_versions.add(i)
100+
101+
return (safe_versions, all_versions-safe_versions)
91102

92103

93104
class rubyAPI:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
gem: sidekiq
3+
osvdb: 125675
4+
url: https://github.com/mperham/sidekiq/pull/2422
5+
title: Sidekiq Gem for Ruby Multiple Unspecified CSRF
6+
date: 2015-07-06
7+
description: Sidekiq::Web lacks CSRF protection
8+
patched_versions:
9+
- ">= 3.4.2"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
gem: sidekiq
3+
osvdb: 125676
4+
url: https://github.com/mperham/sidekiq/issues/2330
5+
title: |
6+
Sidekiq Gem for Ruby web/views/queue.erb CurrentMessagesInQueue Element
7+
Reflected XSS
8+
date: 2015-06-04
9+
description: XSS via queue name in Sidekiq::Web
10+
patched_versions:
11+
- ">= 3.4.0"
12+
related:
13+
osvdb:
14+
- 125677
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
gem: sidekiq
3+
osvdb: 125678
4+
url: https://github.com/mperham/sidekiq/pull/2309
5+
title: Sidekiq Gem for Ruby web/views/queue.erb msg.display_class Element XSS
6+
date: 2015-04-21
7+
description: XSS via job arguments display class in Sidekiq::Web
8+
patched_versions:
9+
- ">= 3.4.0"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
gem: sinatra
3+
cve: 2018-11627
4+
url: https://github.com/sinatra/sinatra/issues/1428
5+
title: XSS via the 400 Bad Request page
6+
date: 2018-05-31
7+
description: |
8+
Sinatra before 2.0.2 has XSS via the 400 Bad Request page that occurs upon a params parser exception.
9+
10+
cvss_v3: 6.1
11+
12+
patched_versions:
13+
- ">= 2.0.2"
14+
15+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
gem: sinatra
3+
cve: 2018-7212
4+
url: https://github.com/sinatra/sinatra/pull/1379
5+
date: 2018-01-09
6+
title: sinatra ruby gem path traversal via backslash characters on Windows
7+
description: |
8+
An issue was discovered in rack-protection/lib/rack/protection/path_traversal.rb
9+
in Sinatra 2.x before 2.0.1 on Windows. Path traversal is possible via backslash
10+
characters.
11+
12+
cvss_v3: 5.3
13+
cvss_v2: 5.0
14+
15+
patched_versions:
16+
- ">= 2.0.1"
17+
18+
unaffected_versions:
19+
- "<= 1.0.0"

vulnerabilities/tests/test_ruby.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import os
2+
import pathlib
3+
from unittest.mock import patch
4+
from unittest import TestCase
5+
from collections import OrderedDict
6+
7+
from packageurl import PackageURL
8+
9+
from vulnerabilities.importers.ruby import rubyDataSource
10+
from vulnerabilities.data_source import GitDataSourceConfiguration
11+
from vulnerabilities.data_source import Advisory
12+
13+
14+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
15+
TEST_DATA = os.path.join(BASE_DIR, 'test_data', 'ruby')
16+
17+
MOCK_ADDED_FILES = []
18+
19+
for filepath in pathlib.Path(TEST_DATA).glob('**/*.yml'):
20+
MOCK_ADDED_FILES.append(filepath.absolute())
21+
22+
23+
class rubyDataSourceTest(TestCase):
24+
25+
@classmethod
26+
def setUpClass(cls):
27+
data_source_cfg = {
28+
'repository_url': 'https://github.com/rubysec/ruby-advisory-db.git', }
29+
cls.data_src = rubyDataSource(1, config=data_source_cfg)
30+
31+
@patch('vulnerabilities.importers.ruby.rubyAPI.get_all_version_of_package',
32+
return_value={'1.0.0', '1.8.0', '2.0.3'})
33+
def test_process_file(self, mock_write):
34+
expected_advisories = {
35+
Advisory(
36+
summary=('An issue was discovered in'
37+
' rack-protection/lib/rack/protection/path_traversal.rb\n'
38+
'in Sinatra 2.x before 2.0.1 on Windows.'
39+
' Path traversal is possible via backslash\ncharacters.\n'),
40+
impacted_package_urls={
41+
PackageURL(
42+
type='gem',
43+
namespace=None,
44+
name='sinatra',
45+
version='1.8.0',
46+
qualifiers=OrderedDict(),
47+
subpath=None)},
48+
resolved_package_urls={
49+
PackageURL(
50+
type='gem',
51+
namespace=None,
52+
name='sinatra',
53+
version='1.0.0',
54+
qualifiers=OrderedDict(),
55+
subpath=None),
56+
PackageURL(
57+
type='gem',
58+
namespace=None,
59+
name='sinatra',
60+
version='2.0.3',
61+
qualifiers=OrderedDict(),
62+
subpath=None)},
63+
reference_urls='https://github.com/sinatra/sinatra/pull/1379',
64+
reference_ids=[],
65+
cve_id='CVE-2018-7212'),
66+
Advisory(
67+
summary=('Sinatra before 2.0.2 has XSS via the 400 Bad Request '
68+
'page that occurs upon a params parser exception.\n'),
69+
impacted_package_urls={
70+
PackageURL(
71+
type='gem',
72+
namespace=None,
73+
name='sinatra',
74+
version='1.0.0',
75+
qualifiers=OrderedDict(),
76+
subpath=None),
77+
PackageURL(
78+
type='gem',
79+
namespace=None,
80+
name='sinatra',
81+
version='1.8.0',
82+
qualifiers=OrderedDict(),
83+
subpath=None)},
84+
resolved_package_urls={
85+
PackageURL(
86+
type='gem',
87+
namespace=None,
88+
name='sinatra',
89+
version='2.0.3',
90+
qualifiers=OrderedDict(),
91+
subpath=None)},
92+
reference_urls='https://github.com/sinatra/sinatra/issues/1428',
93+
reference_ids=[],
94+
cve_id='CVE-2018-11627'),
95+
None}
96+
97+
found_advisories = set()
98+
99+
for p in MOCK_ADDED_FILES:
100+
found_advisories.add(self.data_src._process_file(p))
101+
assert found_advisories == expected_advisories
102+
103+
def test_categorize_versions(self):
104+
105+
all_versions = {'1.0.0', '1.2.0', '9.0.2', '0.2.3'}
106+
safe_ver_ranges = ['==1.0.0', '>1.2.0']
107+
108+
exp_safe_vers = {'1.0.0', '9.0.2'}
109+
exp_aff_vers = {'1.2.0', '0.2.3'}
110+
111+
safe_vers, aff_vers = self.data_src.categorize_versions(
112+
all_versions, safe_ver_ranges)
113+
assert exp_aff_vers == aff_vers
114+
assert exp_safe_vers == safe_vers

0 commit comments

Comments
 (0)