Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
aiohttp==3.7.4
asgiref==3.2.7
attrs==19.3.0
attrs==20.3.0
backcall==0.1.0
beautifulsoup4==4.7.1
cached-property==1.5.1
cffi==1.14.0
contextlib2==0.5.5
decorator==4.4.2
dephell-specifier==0.2.1
univers==21.4.8
dj-database-url==0.4.2
Django==3.0.13
django-filter==2.2.0
Expand All @@ -22,7 +22,7 @@ jedi==0.17.0
lxml==4.6.3
more-itertools==8.0.2
packageurl-python==0.9.3
packaging==19.2
packaging==20.9
parso==0.7.0
pexpect==4.8.0
pickleshare==0.7.5
Expand Down
17 changes: 14 additions & 3 deletions vulnerabilities/data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@

import pygit2
from packageurl import PackageURL
from univers.version_specifier import VersionSpecifier
from univers.versions import version_class_by_package_type

from vulnerabilities.oval_parser import OvalParser
from vulnerabilities.severity_systems import ScoringSystem
Expand Down Expand Up @@ -514,7 +516,7 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
"type" key.

Example value of pkg_metadata:
{"type":"deb","qualifiers":{"distro":"buster"} }
{"type":"deb","qualifiers":{"distro":"buster"} }
"""

all_adv = []
Expand All @@ -536,7 +538,14 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
for package_name in test_data["package_list"]:
if package_name and len(package_name) >= 50:
continue
aff_ver_range = test_data["version_ranges"] or set()

affected_version_range = test_data["version_ranges"] or set()
version_class = version_class_by_package_type[pkg_metadata["type"]]
version_scheme = version_class.scheme

affected_version_range = VersionSpecifier.from_scheme_version_spec_string(
version_scheme, affected_version_range
)
all_versions = self.pkg_manager_api.get(package_name)

# FIXME: what is this 50 DB limit? that's too small for versions
Expand All @@ -546,7 +555,9 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
all_versions = set(filter(lambda x: len(x) < 50, all_versions))
if not all_versions:
continue
affected_versions = set(filter(lambda x: x in aff_ver_range, all_versions))
affected_versions = set(
filter(lambda x: version_class(x) in affected_version_range, all_versions)
)
safe_versions = all_versions - affected_versions

for version in affected_versions:
Expand Down
8 changes: 8 additions & 0 deletions vulnerabilities/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,11 @@ def requests_with_5xx_retry(max_retries=5, backoff_factor=0.5):
session.mount("https://", adapter)
session.mount("http://", adapter)
return session


def contains_alpha(string):
"""
Return True if the input 'string' contains any alphabet
"""

return any([c.isalpha() for c in string])
31 changes: 25 additions & 6 deletions vulnerabilities/importers/apache_kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@

import requests
from bs4 import BeautifulSoup
from dephell_specifier import RangeSpecifier
from packageurl import PackageURL
from univers.versions import MavenVersion
from univers.version_specifier import VersionSpecifier

from vulnerabilities.data_source import Advisory
from vulnerabilities.data_source import DataSource
Expand Down Expand Up @@ -71,13 +72,23 @@ def to_advisory(self, advisory_page):
fixed_packages = [
PackageURL(type="apache", name="kafka", version=version)
for version in self.version_api.get("apache/kafka")
if any([version in version_range for version_range in fixed_version_ranges])
if any(
[
MavenVersion(version) in version_range
for version_range in fixed_version_ranges
]
)
]

affected_packages = [
PackageURL(type="apache", name="kafka", version=version)
for version in self.version_api.get("apache/kafka")
if any([version in version_range for version_range in affected_version_ranges])
if any(
[
MavenVersion(version) in version_range
for version_range in affected_version_ranges
]
)
]

advisories.append(
Expand Down Expand Up @@ -107,14 +118,22 @@ def to_version_ranges(version_range_text):
lower_bound, upper_bound = range_expression.split("to")
lower_bound = f">={lower_bound}"
upper_bound = f"<={upper_bound}"
version_ranges.append(RangeSpecifier(f"{lower_bound},{upper_bound}"))
version_ranges.append(
VersionSpecifier.from_scheme_version_spec_string(
"maven", f"{lower_bound},{upper_bound}"
)
)

elif "and later" in range_expression:
# eg range_expression == "2.1.1 and later"
range_expression = range_expression.replace("and later", "")
version_ranges.append(RangeSpecifier(f">={range_expression}"))
version_ranges.append(
VersionSpecifier.from_scheme_version_spec_string("maven", f">={range_expression}")
)

else:
# eg range_expression == "3.0.0"
version_ranges.append(RangeSpecifier(range_expression))
version_ranges.append(
VersionSpecifier.from_scheme_version_spec_string("maven", range_expression)
)
return version_ranges
32 changes: 20 additions & 12 deletions vulnerabilities/importers/apache_tomcat.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

import requests
from bs4 import BeautifulSoup
from dephell_specifier import RangeSpecifier
from univers.version_specifier import VersionSpecifier
from univers.versions import MavenVersion
from packageurl import PackageURL

from vulnerabilities.data_source import Advisory
Expand Down Expand Up @@ -101,7 +102,7 @@ def to_advisories(self, apache_tomcat_advisory_html):
type="maven", namespace="apache", name="tomcat", version=version
)
for version in self.version_api.get("org.apache.tomcat:tomcat")
if version in version_range
if MavenVersion(version) in version_range
]
)

Expand All @@ -126,16 +127,21 @@ def to_advisories(self, apache_tomcat_advisory_html):

def parse_version_ranges(string):
"""
This method yields Rangespecifier objects obtained by
This method yields VersionSpecifier objects obtained by
parsing `string`.
>> list(parse_version_ranges("Affects: 9.0.0.M1 to 9.0.0.M9"))
[RangeSpecifier(<=9.0.0.M9,>=9.0.0.M1)]

>> list(parse_version_ranges("Affects: 9.0.0.M1"))
[RangeSpecifier(>=9.0.0.M1<=9.0.0.M1)]

>> list(parse_version_ranges("Affects: 9.0.0.M1 to 9.0.0.M9, 1.2.3 to 3.4.5"))
[RangeSpecifier(<=9.0.0.M9,>=9.0.0.M1), RangeSpecifier(<=3.4.5,>=1.2.3)]
>>> list(parse_version_ranges("Affects: 9.0.0.M1 to 9.0.0.M9")) == [
... VersionSpecifier.from_scheme_version_spec_string('maven','<=9.0.0.M9,>=9.0.0.M1')
... ]
True
>>> list(parse_version_ranges("Affects: 9.0.0.M1")) == [
... VersionSpecifier.from_scheme_version_spec_string('maven','>=9.0.0.M1,<=9.0.0.M1')
... ]
True
>>> list(parse_version_ranges("Affects: 9.0.0.M1 to 9.0.0.M9, 1.2.3 to 3.4.5")) == [
... VersionSpecifier.from_scheme_version_spec_string('maven','<=9.0.0.M9,>=9.0.0.M1'),
... VersionSpecifier.from_scheme_version_spec_string('maven','<=3.4.5,>=1.2.3')
... ]
True
"""
version_rng_txt = string.split("Affects:")[-1].strip()
version_ranges = version_rng_txt.split(",")
Expand All @@ -147,4 +153,6 @@ def parse_version_ranges(string):
else:
lower_bound = upper_bound = version_range

yield RangeSpecifier(">=" + lower_bound + "<=" + upper_bound)
yield VersionSpecifier.from_scheme_version_spec_string(
"maven", f">={lower_bound},<={upper_bound}"
)
11 changes: 8 additions & 3 deletions vulnerabilities/importers/elixir_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
from typing import List, Set

import yaml
from dephell_specifier import RangeSpecifier
from univers.version_specifier import VersionSpecifier
from univers.versions import SemverVersion
from packageurl import PackageURL

from vulnerabilities.data_source import GitDataSource
Expand Down Expand Up @@ -86,9 +87,13 @@ def get_versions_for_pkg_from_range_list(self, version_range_list, pkg_name):
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}
version_ranges = [
VersionSpecifier.from_scheme_version_spec_string("semver", r)
for r in version_range_list
]
for version in all_version_list:
if any([version in v for v in version_ranges]):
version_obj = SemverVersion(version)
if any([version_obj in v for v in version_ranges]):
safe_pkg_versions.append(version)

vuln_pkg_versions = set(all_version_list) - set(safe_pkg_versions)
Expand Down
19 changes: 13 additions & 6 deletions vulnerabilities/importers/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
from typing import Optional

import requests
from dephell_specifier import RangeSpecifier
from packageurl import PackageURL
from univers.version_specifier import VersionSpecifier
from univers.versions import version_class_by_package_type

from vulnerabilities.data_source import Advisory
from vulnerabilities.data_source import DataSource
Expand Down Expand Up @@ -200,7 +201,7 @@ def process_response(self) -> List[Advisory]:
ns, pkg_name = self.process_name(ecosystem, name)
aff_range = adv["node"]["vulnerableVersionRange"]
aff_vers, unaff_vers = self.categorize_versions(
aff_range, self.version_api.get(name)
self.version_api.package_type, aff_range, self.version_api.get(name)
)
affected_purls = {
PackageURL(name=pkg_name, namespace=ns, version=version, type=pkg_type)
Expand Down Expand Up @@ -252,8 +253,14 @@ def process_response(self) -> List[Advisory]:

@staticmethod
def categorize_versions(
version_range: str, all_versions: Set[str]
) -> Tuple[Set[str], Set[str]]: # nopep8
version_range = RangeSpecifier(version_range)
affected_versions = {version for version in all_versions if version in version_range}
package_type: str, version_range: str, all_versions: Set[str]
) -> Tuple[Set[str], Set[str]]:
version_class = version_class_by_package_type[package_type]
version_scheme = version_class.scheme
version_range = VersionSpecifier.from_scheme_version_spec_string(
version_scheme, version_range
)
affected_versions = {
version for version in all_versions if version_class(version) in version_range
}
return (affected_versions, all_versions - affected_versions)
13 changes: 9 additions & 4 deletions vulnerabilities/importers/istio.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@
import asyncio
import re
from typing import List, Set

import yaml

from dephell_specifier import RangeSpecifier
from univers.version_specifier import VersionSpecifier
from univers.versions import SemverVersion
from packageurl import PackageURL

from vulnerabilities.data_source import Advisory, GitDataSource, Reference
from vulnerabilities.package_managers import GitHubTagsAPI

Expand Down Expand Up @@ -64,9 +65,13 @@ def get_pkg_versions_from_ranges(self, version_range_list):
all_version = self.version_api.get("istio/istio")
safe_pkg_versions = []
vuln_pkg_versions = []
version_ranges = [RangeSpecifier(r) for r in version_range_list]
version_ranges = [
VersionSpecifier.from_scheme_version_spec_string("semver", r)
for r in version_range_list
]
for version in all_version:
if any([version in v for v in version_ranges]):
version_obj = SemverVersion(version)
if any([version_obj in v for v in version_ranges]):
vuln_pkg_versions.append(version)

safe_pkg_versions = set(all_version) - set(vuln_pkg_versions)
Expand Down
34 changes: 24 additions & 10 deletions vulnerabilities/importers/nginx.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
import requests
from packageurl import PackageURL
from bs4 import BeautifulSoup
from dephell_specifier import RangeSpecifier
from univers.version_specifier import VersionSpecifier
from univers.versions import SemverVersion

from vulnerabilities.data_source import Advisory
from vulnerabilities.data_source import DataSource
Expand Down Expand Up @@ -58,10 +59,10 @@ def set_api(self):

def updated_advisories(self):
advisories = []
if create_etag(data_src=self, url=self.url, etag_key="ETag"):
self.set_api()
data = requests.get(self.url).content
advisories.extend(self.to_advisories(data))
# if create_etag(data_src=self, url=self.url, etag_key="ETag"):
self.set_api()
data = requests.get(self.url).content
advisories.extend(self.to_advisories(data))
return self.batch_advisories(advisories)

def to_advisories(self, data):
Expand Down Expand Up @@ -126,13 +127,15 @@ def extract_fixed_pkgs(self, vuln_info):
raw_ranges = version_info.split(",")
version_ranges = []
for rng in raw_ranges:
# Eg. "1.7.3+" gets converted to RangeSpecifier("^1.7.3")
# Eg. "1.7.3+" gets converted to VersionSpecifier.from_scheme_version_spec_string("semver","^1.7.3")
# The advisory in this case uses `+` in the sense that any version
# with greater or equal `minor` version satisfies the range.
# "1.7.4" satisifes "1.7.3+", but "1.8.4" does not. "1.7.3+" has same
# semantics as that of "^1.7.3"

version_ranges.append(RangeSpecifier("^" + rng[:-1]))
version_ranges.append(
VersionSpecifier.from_scheme_version_spec_string("semver", "^" + rng[:-1])
)

valid_versions = find_valid_versions(self.version_api.get("nginx/nginx"), version_ranges)

Expand All @@ -148,16 +151,26 @@ def extract_vuln_pkgs(self, vuln_info):
version_ranges = []
windows_only = False
for version_info in version_infos.split(", "):
if version_info == "all":
# This is misleading since eventually some version get fixed.
continue

if "-" not in version_info:
# These are discrete versions
version_ranges.append(RangeSpecifier(version_info[0]))
version_ranges.append(
VersionSpecifier.from_scheme_version_spec_string("semver", version_info[0])
)
continue

windows_only = "nginx/Windows" in version_info
version_info = version_info.replace("nginx/Windows", "")
lower_bound, upper_bound = version_info.split("-")

version_ranges.append(RangeSpecifier(f">={lower_bound},<={upper_bound}"))
version_ranges.append(
VersionSpecifier.from_scheme_version_spec_string(
"semver", f">={lower_bound},<={upper_bound}"
)
)

valid_versions = find_valid_versions(self.version_api.get("nginx/nginx"), version_ranges)
qualifiers = {}
Expand All @@ -173,7 +186,8 @@ def extract_vuln_pkgs(self, vuln_info):
def find_valid_versions(versions, version_ranges):
valid_versions = set()
for version in versions:
if any([version in ver_range for ver_range in version_ranges]):
version_obj = SemverVersion(version)
if any([version_obj in ver_range for ver_range in version_ranges]):
valid_versions.add(version)

return valid_versions
Loading