Skip to content

Commit 4ab3633

Browse files
committed
Add ruby importer and its tests
Signed-off-by: sbs2001 <shivam.sandbhor@gmail.com>
1 parent 7426253 commit 4ab3633

4 files changed

Lines changed: 161 additions & 23 deletions

File tree

requirements.txt

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
1-
setuptools==36.5.0
2-
1+
attrs==19.3.0
32
beautifulsoup4==4.7.1
4-
lxml==4.3.3
5-
django==2.2.8
6-
djangorestframework==3.9.2
3+
dephell-specifier==0.2.1
4+
dj-database-url==0.4.2
5+
Django==2.2.8
76
django-filter==2.2.0
7+
djangorestframework==3.9.2
8+
gunicorn==19.7.1
9+
importlib-metadata==1.3.0
10+
lxml==4.3.3
11+
more-itertools==8.0.2
812
packageurl-python==0.8.7
9-
semantic-version==2.8.2
10-
11-
# Tests
12-
pytest==5.3.2
13-
pytest-django==3.7.0
13+
packaging==19.2
14+
pluggy==0.13.1
15+
psycopg2==2.8.4
16+
py==1.8.0
1417
pycodestyle==2.5.0
15-
16-
# Deployment
17-
gunicorn==19.7.1
18+
pyparsing==2.4.5
19+
pytest==5.3.2
20+
pytest-dependency==0.4.0
21+
pytest-django==3.7.0
22+
pytest-mock==1.13.0
23+
pytz==2019.3
24+
PyYAML==5.3
25+
saneyaml==0.4
26+
semantic-version==2.8.2
27+
six==1.13.0
28+
soupsieve==1.9.5
29+
sqlparse==0.3.0
30+
tqdm==4.41.1
31+
wcwidth==0.1.7
1832
whitenoise==5.0.1
19-
dj_database_url==0.4.2
20-
psycopg2==2.8.4
33+
zipp==0.6.0

vulnerabilities/data_dump.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,14 @@ def archlinux_dump(extract_data):
144144

145145
for ap in affected_packages:
146146
ImpactedPackage.objects.get_or_create(
147-
vulnerability=vulnerability,
148-
package=ap,
147+
vulnerability=vulnerability,
148+
package=ap,
149149
)
150150

151151
for fp in fixed_packages:
152152
ResolvedPackage.objects.get_or_create(
153-
vulnerability=vulnerability,
154-
package=fp,
153+
vulnerability=vulnerability,
154+
package=fp,
155155
)
156156

157157

@@ -190,3 +190,39 @@ def npm_dump(extract_data):
190190
vulnerability=vulnerability,
191191
package=package_fixed
192192
)
193+
194+
195+
def ruby_dump(extract_data):
196+
for package_data in extract_data:
197+
198+
vulnerability, _ = Vulnerability.objects.get_or_create(
199+
cve_id=package_data['cve_id'],
200+
summary=package_data['summary'],
201+
)
202+
203+
VulnerabilityReference.objects.get_or_create(
204+
vulnerability=vulnerability,
205+
url=package_data['advisory']
206+
)
207+
208+
for version in package_data['affected_versions']:
209+
affected_package = Package.objects.create(
210+
name=package_data['package_name'],
211+
type='gem',
212+
version=version
213+
)
214+
ImpactedPackage.objects.create(
215+
vulnerability=vulnerability,
216+
package=affected_package
217+
)
218+
219+
for version in package_data['fixed_versions']:
220+
unaffected_package = Package.objects.create(
221+
name=package_data['package_name'],
222+
type='gem',
223+
version=version
224+
)
225+
ResolvedPackage.objects.create(
226+
vulnerability=vulnerability,
227+
package=unaffected_package
228+
)

vulnerabilities/management/commands/import.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,25 @@
2424
from django.core.management.base import BaseCommand, CommandError
2525

2626
from vulnerabilities import data_dump as dd
27-
from vulnerabilities.scraper import debian, ubuntu, archlinux, npm
27+
from vulnerabilities.scraper import debian, ubuntu, archlinux, npm, ruby
2828

2929
IMPORTERS = {
30+
'ruby': lambda: dd.ruby_dump(ruby.import_vulnerabilities()),
3031
'npm': lambda: dd.npm_dump(npm.scrape_vulnerabilities()),
3132
'debian': lambda: dd.debian_dump(debian.scrape_vulnerabilities()),
3233
'ubuntu': lambda: dd.ubuntu_dump(ubuntu.scrape_cves()),
33-
'archlinux': lambda: dd.archlinux_dump(archlinux.scrape_vulnerabilities()),
34+
'archlinux': lambda: dd.archlinux_dump(archlinux.scrape_vulnerabilities())
3435
}
3536

3637

3738
class Command(BaseCommand):
3839
help = 'Import vulnerability data'
3940

4041
def add_arguments(self, parser):
41-
parser.add_argument('--list', action='store_true', help='List available data sources')
42+
parser.add_argument(
43+
'--list',
44+
action='store_true',
45+
help='List available data sources')
4246

4347
parser.add_argument('--all', action='store_true',
4448
help='Import data from all available sources')
@@ -69,7 +73,8 @@ def validate_sources(self, sources):
6973
raise CommandError(f'Unknown data sources: {unknown}')
7074

7175
def list_sources(self):
72-
self.stdout.write('Vulnerability data can be imported from the following sources:')
76+
self.stdout.write(
77+
'Vulnerability data can be imported from the following sources:')
7378
self.stdout.write(', '.join(IMPORTERS.keys()))
7479

7580
def import_data(self, sources):

vulnerabilities/scraper/ruby.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import os
2+
import urllib.request
3+
from urllib.error import HTTPError
4+
from zipfile import ZipFile
5+
from io import BytesIO
6+
import saneyaml
7+
from dephell_specifier import RangeSpecifier
8+
from urllib.request import urlopen
9+
10+
RUBYSEC_DB_URL = 'https://github.com/rubysec/ruby-advisory-db/archive/master.zip'
11+
12+
13+
def rubygem_advisories(url, prefix='ruby-advisory-db-master/gems/'):
14+
with urlopen(url) as response:
15+
with ZipFile(BytesIO(response.read())) as zf:
16+
for path in zf.namelist():
17+
if path.startswith(prefix) and path.endswith('.yml'):
18+
yield saneyaml.load(zf.open(path))
19+
20+
21+
def get_all_versions_of_package(package_name):
22+
url_to_load = 'https://rubygems.org/api/v1/versions/' + package_name + '.yaml'
23+
try:
24+
page = urllib.request.urlopen(url_to_load)
25+
package_history = saneyaml.load(page)
26+
except HTTPError:
27+
return []
28+
for version in package_history:
29+
yield version['number']
30+
31+
32+
def get_patched_range(spec_list):
33+
spec_list = [string.replace(' ', '') for string in spec_list]
34+
for spec in spec_list:
35+
if 'rc' in spec:
36+
continue
37+
yield RangeSpecifier(spec)
38+
39+
40+
def import_vulnerabilities():
41+
ids = set()
42+
vulnerability_package_dicts = []
43+
for vulnerability in rubygem_advisories(RUBYSEC_DB_URL):
44+
45+
package_name = vulnerability.get(
46+
'gem')
47+
48+
if not package_name:
49+
continue
50+
51+
summary = vulnerability.get('description', '')
52+
53+
if 'cve' in vulnerability:
54+
vulnerability_id = 'CVE-{}'.format(vulnerability['cve'])
55+
if vulnerability_id in ids:
56+
continue
57+
ids.add(vulnerability_id)
58+
else:
59+
continue
60+
61+
advisory_url = vulnerability.get('url')
62+
patched_version_ranges = list(
63+
get_patched_range(
64+
vulnerability.get('patched_versions', [])))
65+
all_versions = set(get_all_versions_of_package(package_name))
66+
unaffected_versions = set()
67+
68+
if patched_version_ranges:
69+
for version in all_versions:
70+
for spec in patched_version_ranges:
71+
if version in spec:
72+
unaffected_versions.add(version)
73+
break
74+
75+
affected_versions = all_versions - unaffected_versions
76+
vulnerability_package_dicts.append({
77+
'package_name': package_name,
78+
'summary': summary,
79+
'cve_id': vulnerability_id,
80+
'fixed_versions': unaffected_versions,
81+
'affected_versions': affected_versions,
82+
'advisory': advisory_url
83+
})
84+
return vulnerability_package_dicts

0 commit comments

Comments
 (0)