diff --git a/AUTHORS.rst b/AUTHORS.rst index a7fe33387..039459f93 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,4 +10,5 @@ The following organizations or individuals have contributed to this repo: - Ayush Lohani @lohani2280 - Islam Elhakmi @EslamHiko - Edoardo Lanzini @elanzini -- Navonil Das @NavonilDas \ No newline at end of file +- Navonil Das @NavonilDas +- Tushar Upadhyay @tushar912 \ No newline at end of file diff --git a/SOURCES.rst b/SOURCES.rst index 02b2ff6fd..4bba04be2 100644 --- a/SOURCES.rst +++ b/SOURCES.rst @@ -43,3 +43,5 @@ +----------------+------------------------------------------------------------------------------------------------------+----------------------------------------------------+ |postgresql | https://www.postgresql.org/support/security/ |postgresql | +----------------+------------------------------------------------------------------------------------------------------+----------------------------------------------------+ +|elixir_security | https://github.com/dependabot/elixir-security-advisories |hex packages | ++----------------+------------------------------------------------------------------------------------------------------+----------------------------------------------------+ diff --git a/vulnerabilities/importer_yielder.py b/vulnerabilities/importer_yielder.py index 5b9fdc641..9400979b3 100644 --- a/vulnerabilities/importer_yielder.py +++ b/vulnerabilities/importer_yielder.py @@ -226,6 +226,15 @@ 'data_source': 'PostgreSQLDataSource', 'data_source_cfg': {}, }, + { + 'name': 'elixir_security', + 'license': 'cc0-1.0', + 'last_run': None, + 'data_source': 'ElixirSecurityDataSource', + 'data_source_cfg': { + 'repository_url': 'https://github.com/dependabot/elixir-security-advisories' + }, + }, { 'name': 'apache_tomcat', 'license': '', diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 73b081ce2..14e82a77f 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -22,25 +22,26 @@ from vulnerabilities.importers.alpine_linux import AlpineDataSource +from vulnerabilities.importers.apache_httpd import ApacheHTTPDDataSource from vulnerabilities.importers.archlinux import ArchlinuxDataSource from vulnerabilities.importers.debian import DebianDataSource -from vulnerabilities.importers.npm import NpmDataSource -from vulnerabilities.importers.rust import RustDataSource -from vulnerabilities.importers.safety_db import SafetyDbDataSource -from vulnerabilities.importers.ruby import RubyDataSource -from vulnerabilities.importers.ubuntu import UbuntuDataSource -from vulnerabilities.importers.retiredotnet import RetireDotnetDataSource -from vulnerabilities.importers.suse_backports import SUSEBackportsDataSource from vulnerabilities.importers.debian_oval import DebianOvalDataSource -from vulnerabilities.importers.redhat import RedhatDataSource +from vulnerabilities.importers.elixir_security import ElixirSecurityDataSource from vulnerabilities.importers.gentoo import GentooDataSource -from vulnerabilities.importers.openssl import OpenSSLDataSource -from vulnerabilities.importers.ubuntu_usn import UbuntuUSNDataSource from vulnerabilities.importers.github import GitHubAPIDataSource -from vulnerabilities.importers.nvd import NVDDataSource -from vulnerabilities.importers.project_kb_msr2019 import ProjectKBMSRDataSource -from vulnerabilities.importers.apache_httpd import ApacheHTTPDDataSource from vulnerabilities.importers.kaybee import KaybeeDataSource from vulnerabilities.importers.nginx import NginxDataSource +from vulnerabilities.importers.npm import NpmDataSource +from vulnerabilities.importers.nvd import NVDDataSource +from vulnerabilities.importers.openssl import OpenSSLDataSource from vulnerabilities.importers.postgresql import PostgreSQLDataSource +from vulnerabilities.importers.project_kb_msr2019 import ProjectKBMSRDataSource +from vulnerabilities.importers.redhat import RedhatDataSource +from vulnerabilities.importers.retiredotnet import RetireDotnetDataSource +from vulnerabilities.importers.ruby import RubyDataSource +from vulnerabilities.importers.rust import RustDataSource +from vulnerabilities.importers.safety_db import SafetyDbDataSource +from vulnerabilities.importers.suse_backports import SUSEBackportsDataSource +from vulnerabilities.importers.ubuntu import UbuntuDataSource +from vulnerabilities.importers.ubuntu_usn import UbuntuUSNDataSource from vulnerabilities.importers.apache_tomcat import ApacheTomcatDataSource diff --git a/vulnerabilities/importers/elixir_security.py b/vulnerabilities/importers/elixir_security.py new file mode 100644 index 000000000..9293b0ded --- /dev/null +++ b/vulnerabilities/importers/elixir_security.py @@ -0,0 +1,144 @@ +# Copyright (c) nexB Inc. and others. All rights reserved. +# http://nexb.com and https://github.com/nexB/vulnerablecode/ +# The VulnerableCode software is licensed under the Apache License version 2.0. +# Data generated with VulnerableCode require an acknowledgment. +# +# You may not use this software except in compliance with the License. +# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# When you publish or redistribute any data created with VulnerableCode or any VulnerableCode +# derivative work, you must accompany this data with the following acknowledgment: +# +# Generated with VulnerableCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, either express or implied. No content created from +# VulnerableCode should be considered or used as legal advice. Consult an Attorney +# for any legal advice. +# VulnerableCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/vulnerablecode/ for support and download. + +import asyncio +from typing import List, Set + +import yaml +from dephell_specifier import RangeSpecifier +from packageurl import PackageURL + +from vulnerabilities.data_source import GitDataSource +from vulnerabilities.data_source import Advisory +from vulnerabilities.data_source import Reference +from vulnerabilities.package_managers import HexVersionAPI +from vulnerabilities.helpers import load_yaml + + +class ElixirSecurityDataSource(GitDataSource): + def __enter__(self): + super(ElixirSecurityDataSource, self).__enter__() + + if not getattr(self, "_added_files", None): + self._added_files, self._updated_files = self.file_changes( + recursive=True, file_ext="yml", subdir="./packages" + ) + self.pkg_manager_api = HexVersionAPI() + self.set_api(self.collect_packages()) + + def set_api(self, packages): + asyncio.run(self.pkg_manager_api.load_api(packages)) + + def updated_advisories(self) -> Set[Advisory]: + files = self._updated_files + advisories = [] + for f in files: + processed_data = self.process_file(f) + if processed_data: + advisories.append(processed_data) + return self.batch_advisories(advisories) + + def added_advisories(self) -> Set[Advisory]: + files = self._added_files + advisories = [] + for f in files: + processed_data = self.process_file(f) + if processed_data: + advisories.append(processed_data) + return self.batch_advisories(advisories) + + def collect_packages(self): + packages = set() + files = self._updated_files.union(self._added_files) + for f in files: + data = load_yaml(f) + if data.get("package"): + packages.add(data["package"]) + + return packages + + def get_versions_for_pkg_from_range_list(self, version_range_list, pkg_name): + # Takes a list of version ranges(pathced and unaffected) of a package + # as parameter and returns a tuple of safe package versions and + # vulnerable package versions + + safe_pkg_versions = [] + vuln_pkg_versions = [] + all_version_list = self.pkg_manager_api.get(pkg_name) + if not version_range_list: + return [], all_version_list + version_ranges = {RangeSpecifier(r) for r in version_range_list} + for version in all_version_list: + if any([version in v for v in version_ranges]): + safe_pkg_versions.append(version) + + vuln_pkg_versions = set(all_version_list) - set(safe_pkg_versions) + return safe_pkg_versions, vuln_pkg_versions + + def process_file(self, path): + yaml_file = load_yaml(path) + pkg_name = yaml_file["package"] + safe_pkg_versions = [] + vuln_pkg_versions = [] + if not yaml_file.get("patched_versions"): + yaml_file["patched_versions"] = [] + + if not yaml_file.get("unaffected_versions"): + yaml_file["unaffected_versions"] = [] + + safe_pkg_versions, vuln_pkg_versions = self.get_versions_for_pkg_from_range_list( + yaml_file["patched_versions"] + yaml_file["unaffected_versions"], + pkg_name, + ) + + if yaml_file.get("cve"): + cve_id = "CVE-" + yaml_file["cve"] + else: + cve_id = "" + + safe_purls = [] + vuln_purls = [] + + safe_purls = { + PackageURL(name=pkg_name, type="hex", version=version) for version in safe_pkg_versions + } + + vuln_purls = { + PackageURL(name=pkg_name, type="hex", version=version) for version in vuln_pkg_versions + } + + vuln_references = [ + Reference( + reference_id=yaml_file["id"], + ), + Reference( + url=yaml_file["link"], + ), + ] + + return Advisory( + summary=yaml_file["description"], + impacted_package_urls=vuln_purls, + resolved_package_urls=safe_purls, + cve_id=cve_id, + vuln_references=vuln_references, + ) diff --git a/vulnerabilities/package_managers.py b/vulnerabilities/package_managers.py index e8edc09bc..44ad6d367 100644 --- a/vulnerabilities/package_managers.py +++ b/vulnerabilities/package_managers.py @@ -351,3 +351,24 @@ async def fetch(self, owner_repo: str, session) -> None: resp = await session.request(method="GET", url=endpoint) resp = await resp.json() self.cache[owner_repo] = [release["ref"].split("/")[-1] for release in resp] + + +class HexVersionAPI(VersionAPI): + async def load_api(self, pkg_set): + async with ClientSession(raise_for_status=True) as session: + await asyncio.gather( + *[self.fetch(pkg, session) for pkg in pkg_set if pkg not in self.cache] + ) + + async def fetch(self, pkg, session): + url = f"https://hex.pm/api/packages/{pkg}" + versions = set() + try: + response = await session.request(method="GET", url=url) + response = await response.json() + for release in response["releases"]: + versions.add(release["version"]) + except (ClientResponseError, JSONDecodeError): + pass + + self.cache[pkg] = versions diff --git a/vulnerabilities/tests/test_data/elixir_security/test_file.yml b/vulnerabilities/tests/test_data/elixir_security/test_file.yml new file mode 100644 index 000000000..519cc60f9 --- /dev/null +++ b/vulnerabilities/tests/test_data/elixir_security/test_file.yml @@ -0,0 +1,12 @@ +--- +id: 2aae6e3a-24a3-4d5f-86ff-b964eaf7c6d1 +package: coherence +disclosure_date: 2017-08-02 +cve: 2018-20301 +link: https://github.com/smpallen99/coherence/issues/270 +title: | + Permissive parameters and privilege escalation +description: | + The Coherence library has "Mass Assignment"-like vulnerabilities. +patched_versions: + - ">= 0.5.2" \ No newline at end of file diff --git a/vulnerabilities/tests/test_elixir_security.py b/vulnerabilities/tests/test_elixir_security.py new file mode 100644 index 000000000..717189c98 --- /dev/null +++ b/vulnerabilities/tests/test_elixir_security.py @@ -0,0 +1,137 @@ +# Copyright (c) nexB Inc. and others. All rights reserved. +# http://nexb.com and https://github.com/nexB/vulnerablecode/ +# The VulnerableCode software is licensed under the Apache License version 2.0. +# Data generated with VulnerableCode require an acknowledgment. +# +# You may not use this software except in compliance with the License. +# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# When you publish or redistribute any data created with VulnerableCode or any VulnerableCode +# derivative work, you must accompany this data with the following acknowledgment: +# +# Generated with VulnerableCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, either express or implied. No content created from +# VulnerableCode should be considered or used as legal advice. Consult an Attorney +# for any legal advice. +# VulnerableCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/vulnerablecode/ for support and download. + +import os +from collections import OrderedDict +from unittest import TestCase + +from packageurl import PackageURL + +from vulnerabilities.data_source import Advisory +from vulnerabilities.data_source import Reference +from vulnerabilities.importers.elixir_security import ElixirSecurityDataSource +from vulnerabilities.package_managers import HexVersionAPI + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class TestElixirSecurityDataSource(TestCase): + @classmethod + def setUpClass(cls): + data_source_cfg = { + "repository_url": "https://github.com/dependabot/elixir-security-advisories", + } + cls.data_src = ElixirSecurityDataSource(1, config=data_source_cfg) + cls.data_src.pkg_manager_api = HexVersionAPI( + { + "coherence": [ + "0.5.2", + "0.5.1", + "0.5.0", + "0.4.0", + "0.3.1", + "0.3.0", + "0.2.0", + "0.1.3", + "0.1.2", + "0.1.1", + "0.1.0", + ] + } + ) + + def test_process_file(self): + + path = os.path.join(BASE_DIR, "test_data/elixir_security/test_file.yml") + expected_data = Advisory( + summary=('The Coherence library has "Mass Assignment"-like vulnerabilities.\n'), + impacted_package_urls={ + PackageURL( + type="hex", + name="coherence", + version="0.5.1", + ), + PackageURL( + type="hex", + name="coherence", + version="0.5.0", + ), + PackageURL( + type="hex", + name="coherence", + version="0.4.0", + ), + PackageURL( + type="hex", + name="coherence", + version="0.3.1", + ), + PackageURL( + type="hex", + name="coherence", + version="0.3.0", + ), + PackageURL( + type="hex", + name="coherence", + version="0.2.0", + ), + PackageURL( + type="hex", + name="coherence", + version="0.1.3", + ), + PackageURL( + type="hex", + name="coherence", + version="0.1.2", + ), + PackageURL( + type="hex", + name="coherence", + version="0.1.1", + ), + PackageURL( + type="hex", + name="coherence", + version="0.1.0", + ), + }, + resolved_package_urls={ + PackageURL( + type="hex", + name="coherence", + version="0.5.2", + ), + }, + vuln_references=[ + Reference( + reference_id="2aae6e3a-24a3-4d5f-86ff-b964eaf7c6d1", + ), + Reference(url="https://github.com/smpallen99/coherence/issues/270"), + ], + cve_id="CVE-2018-20301", + ) + + found_data = self.data_src.process_file(path) + + assert expected_data == found_data