|
| 1 | +import os |
| 2 | +import urllib.request |
| 3 | +import saneyaml |
| 4 | + |
| 5 | +from shutil import rmtree |
| 6 | +from urllib.error import HTTPError |
| 7 | +from zipfile import ZipFile |
| 8 | +from itertools import chain |
| 9 | +from dephell_specifier import RangeSpecifier |
| 10 | + |
| 11 | +RUBYCVE_LINK = 'https://github.com/rubysec/ruby-advisory-db/archive/master.zip' |
| 12 | +DOWNLOAD_PATH = os.path.dirname(os.path.realpath(__file__)) |
| 13 | + |
| 14 | + |
| 15 | +def get_rubycve_db(): |
| 16 | + pathToZip, _ = urllib.request.urlretrieve( |
| 17 | + RUBYCVE_LINK, os.path.join( |
| 18 | + DOWNLOAD_PATH, 'ruby.zip')) |
| 19 | + ZipFile(pathToZip).extractall(DOWNLOAD_PATH) |
| 20 | + os.remove(pathToZip) |
| 21 | + |
| 22 | + |
| 23 | +def path_of_yaml_of_all_packages(): |
| 24 | + gemPath = os.path.join(DOWNLOAD_PATH, 'ruby-advisory-db-master', 'gems') |
| 25 | + rubiesPath = os.path.join( |
| 26 | + DOWNLOAD_PATH, |
| 27 | + 'ruby-advisory-db-master', |
| 28 | + 'rubies') |
| 29 | + for ( |
| 30 | + packagePath, |
| 31 | + _, |
| 32 | + yamlNames) in chain( |
| 33 | + os.walk(gemPath), |
| 34 | + os.walk(rubiesPath)): |
| 35 | + for yamlName in yamlNames: |
| 36 | + yield os.path.join(packagePath, yamlName) |
| 37 | + |
| 38 | + |
| 39 | +def get_all_versions_of_package(package_name): |
| 40 | + url_to_load = 'https://rubygems.org/api/v1/versions/' + package_name + '.yaml' |
| 41 | + try: |
| 42 | + page = urllib.request.urlopen(url_to_load) |
| 43 | + package_history = saneyaml.load(page) |
| 44 | + except HTTPError: |
| 45 | + return [] |
| 46 | + for version in package_history: |
| 47 | + yield version['number'] |
| 48 | + |
| 49 | + |
| 50 | +def get_patched_range(spec_list): |
| 51 | + if spec_list: |
| 52 | + def remove_space(string): return string.replace(' ', '') |
| 53 | + spec_list = list(map(remove_space, spec_list)) |
| 54 | + for spec in spec_list: |
| 55 | + if 'rc' in spec: |
| 56 | + continue |
| 57 | + yield RangeSpecifier(spec) |
| 58 | + else: |
| 59 | + return None |
| 60 | + |
| 61 | + |
| 62 | +def import_vulnerabilities(): |
| 63 | + get_rubycve_db() |
| 64 | + ids = set() |
| 65 | + vulnerability_to_package_map = [] |
| 66 | + for vulnerability_path in path_of_yaml_of_all_packages(): |
| 67 | + with open(vulnerability_path) as yamlFile: |
| 68 | + vulnerability = saneyaml.load(yamlFile) |
| 69 | + package_name = vulnerability.get( |
| 70 | + 'engine', vulnerability.get('gem')) |
| 71 | + summary = vulnerability.get('description', '') |
| 72 | + if 'cve' in vulnerability: |
| 73 | + vulnerability_id = 'CVE-{}'.format(vulnerability['cve']) |
| 74 | + if vulnerability_id in ids: |
| 75 | + continue |
| 76 | + ids.add(vulnerability_id) |
| 77 | + else: |
| 78 | + continue |
| 79 | + severity = vulnerability.get( |
| 80 | + 'cvss_v3', vulnerability.get('cvss_v2')) |
| 81 | + advisoryUrl = vulnerability.get('url') |
| 82 | + specs = list( |
| 83 | + get_patched_range( |
| 84 | + vulnerability.get('patched_versions'))) |
| 85 | + allVersions = set(list(get_all_versions_of_package(package_name))) |
| 86 | + unaffected_versions = set() |
| 87 | + if specs: |
| 88 | + for version in allVersions: |
| 89 | + for spec in specs: |
| 90 | + if(version in spec): |
| 91 | + unaffected_versions.add(version) |
| 92 | + break |
| 93 | + affected_versions = allVersions - unaffected_versions |
| 94 | + vulnerability_to_package_map.append({ |
| 95 | + 'package_name': package_name, |
| 96 | + 'summary': summary, |
| 97 | + 'cve_id': vulnerability_id, |
| 98 | + 'fixed_versions': unaffected_versions, |
| 99 | + 'affected_versions': affected_versions, |
| 100 | + 'severity': severity, |
| 101 | + 'advisory': advisoryUrl |
| 102 | + }) |
| 103 | + rmtree(os.path.join(DOWNLOAD_PATH, 'ruby-advisory-db-master')) |
| 104 | + return vulnerability_to_package_map |
0 commit comments