Skip to content
9 changes: 9 additions & 0 deletions vulnerabilities/importer_yielder.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,15 @@
'data_source': 'PostgreSQLDataSource',
'data_source_cfg': {},
},
{
'name': 'elixir_security',
'license': '',
'last_run': None,
'data_source': 'ElixirSecurityDataSource',
'data_source_cfg': {
'repository_url': 'https://github.com/dependabot/elixir-security-advisories'
},
},

]

Expand Down
27 changes: 14 additions & 13 deletions vulnerabilities/importers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,25 @@


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
139 changes: 139 additions & 0 deletions vulnerabilities/importers/elixir_security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# 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
Comment thread
tushar912 marked this conversation as resolved.
from dephell_specifier import RangeSpecifier
from packageurl import PackageURL

from vulnerabilities.data_source import GitDataSource
from vulnerabilities.data_source import GitDataSourceConfiguration

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import is not required

from vulnerabilities.data_source import Advisory
from vulnerabilities.data_source import Reference
from vulnerabilities.package_managers import HexVersionAPI


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_from_range(self, version_list, pkg_name):

@sbs2001 sbs2001 Dec 22, 2020

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment or docstring would be very helpful here.

Actually this function doesn't make sense it's called get_versions_from_range but the parameters don't have suggest any sort of range

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plus the name of package is required, which the name of the function doesn't suggest

@tushar912 tushar912 Dec 22, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should I rename it as get_versions_for_pkg_from_range_list. I would rename the parameter version_list to version_range_list. I would make sure to add comment also

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's verbose, but fine by me :) . A better design would be to decouple the obtaining of all versions of package from categorising the versions.

pkg_versions = []
if not getattr(self, 'pkg_manager_api', None):
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
self.pkg_manager_api = HexVersionAPI()
all_version_list = self.pkg_manager_api.get(
pkg_name)
if version_list is None:
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
return
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
version_ranges = {RangeSpecifier(r) for r in version_list}
for version in all_version_list:
if any([version in v for v in version_ranges]):
pkg_versions.append(version)
return pkg_versions

def process_file(self, path):
yaml_file = load_yaml(path)
pkg_name = yaml_file["package"]
safe_pkg_versions = []
if yaml_file.get("unaffected_versions"):
safe_pkg_versions = self.get_versions_from_range(
yaml_file["patched_versions"] +
yaml_file["unaffected_versions"],
pkg_name,
)
else:
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
safe_pkg_versions = self.get_versions_from_range(
yaml_file["patched_versions"], pkg_name
)
if yaml_file.get('cve'):
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
cve_id = "CVE-" + yaml_file["cve"]
else:
cve_id = ""

safe_purls = []
if safe_pkg_versions is not None:
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
safe_purls = {
PackageURL(name=pkg_name, type="hex", version=version)
for version in safe_pkg_versions
}

vuln_reference = [
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
Reference(
reference_id=yaml_file["id"],
),
Reference(
url=yaml_file["link"],
)
]

return Advisory(
summary=yaml_file["description"],
impacted_package_urls=[],
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
resolved_package_urls=safe_purls,
cve_id=cve_id,
vuln_references=vuln_reference,
)


def load_yaml(path):
with open(path) as f:
return yaml.safe_load(f)
21 changes: 21 additions & 0 deletions vulnerabilities/package_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,24 @@ async def fetch(self, owner_repo: str, session) -> None:
resp = await resp.json()
print(resp)
self.cache[owner_repo] = [release["ref"].split("/")[-1] for release in resp]


class HexVersionAPI(VersionAPI):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

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):
Comment thread
sbs2001 marked this conversation as resolved.
pass

self.cache[pkg] = versions
12 changes: 12 additions & 0 deletions vulnerabilities/tests/test_data/elixir_security/test_file.yml
Original file line number Diff line number Diff line change
@@ -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"
83 changes: 83 additions & 0 deletions vulnerabilities/tests/test_elixir_security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# 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': [
Comment thread
sbs2001 marked this conversation as resolved.
Outdated
"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=[],
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