Skip to content

Commit e36d9e1

Browse files
authored
Merge pull request #1476 from OmkarPh/feat/safetydb
Added safetydb datasource
2 parents 045fedd + 107cdaf commit e36d9e1

File tree

7 files changed

+236
-1
lines changed

7 files changed

+236
-1
lines changed

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ addopts = [
5959
"--ignore=vulnerabilities/importers/retiredotnet.py",
6060
"--ignore=vulnerabilities/importers/ruby.py",
6161
"--ignore=vulnerabilities/importers/rust.py",
62-
"--ignore=vulnerabilities/importers/safety_db.py",
6362
"--ignore=vulnerabilities/importers/suse_backports.py",
6463
"--ignore=vulnerabilities/importers/suse_scores.py",
6564
"--ignore=vulnerabilities/importers/ubuntu_usn.py",

vulntotal/datasources/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
from vulntotal.datasources import gitlab
1313
from vulntotal.datasources import oss_index
1414
from vulntotal.datasources import osv
15+
from vulntotal.datasources import safetydb
1516
from vulntotal.datasources import snyk
1617
from vulntotal.datasources import vulnerablecode
1718
from vulntotal.validator import DataSource
1819

1920
DATASOURCE_REGISTRY = {
2021
"deps": deps.DepsDataSource,
2122
"github": github.GithubDataSource,
23+
"safetydb": safetydb.SafetydbDataSource,
2224
"gitlab": gitlab.GitlabDataSource,
2325
"oss_index": oss_index.OSSDataSource,
2426
"osv": osv.OSVDataSource,

vulntotal/datasources/safetydb.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/nexB/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import logging
11+
from typing import Iterable
12+
from typing import List
13+
14+
import requests
15+
from packageurl import PackageURL
16+
17+
from vulntotal.validator import DataSource
18+
from vulntotal.validator import InvalidCVEError
19+
from vulntotal.validator import VendorData
20+
21+
logger = logging.getLogger(__name__)
22+
23+
24+
class SafetydbDataSource(DataSource):
25+
spdx_license_expression = "CC-BY-NC-4.0"
26+
license_url = "https://github.com/pyupio/safety-db/blob/master/LICENSE.txt"
27+
url = "https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json"
28+
29+
def fetch_advisory(self):
30+
"""
31+
Fetch entire JSON advisory from pyupio repository
32+
33+
Parameters:
34+
35+
Returns:
36+
A JSON object containing the advisory information for insecure packages, or None if an error occurs while fetching data from safetydb repo's URL.
37+
"""
38+
39+
response = requests.get(self.url)
40+
try:
41+
response.raise_for_status()
42+
except requests.exceptions.HTTPError as e:
43+
logger.error(f"Error while fetching safetydb advisories: {e}")
44+
return
45+
46+
return response.json()
47+
48+
def datasource_advisory(self, purl) -> Iterable[VendorData]:
49+
if purl.type not in self.supported_ecosystem():
50+
return []
51+
advisory = self.fetch_advisory()
52+
self._raw_dump.append(advisory)
53+
return parse_advisory(advisory, purl)
54+
55+
def datasource_advisory_from_cve(self, cve: str) -> Iterable[VendorData]:
56+
if not cve.upper().startswith("CVE-"):
57+
raise InvalidCVEError
58+
advisory = self.fetch_advisory()
59+
self._raw_dump.append(advisory)
60+
return parse_advisory_for_cve(advisory, cve)
61+
62+
@classmethod
63+
def supported_ecosystem(cls):
64+
return {"pypi": "PyPI"}
65+
66+
67+
def parse_advisory(response, purl: PackageURL) -> Iterable[VendorData]:
68+
"""
69+
Parse response from safetydb API and yield VendorData
70+
71+
Parameters:
72+
response: A JSON object containing the response data from the safetydb datasource.
73+
74+
Yields:
75+
VendorData instance containing the advisory information for the package.
76+
"""
77+
78+
for advisory in response.get(purl.name, []):
79+
yield VendorData(
80+
purl=PackageURL(purl.type, purl.namespace, purl.name),
81+
aliases=[advisory.get("cve"), advisory.get("id")],
82+
affected_versions=sorted(advisory.get("specs")),
83+
fixed_versions=[],
84+
)
85+
86+
87+
def parse_advisory_for_cve(response, cve: str) -> Iterable[VendorData]:
88+
"""
89+
Parse response from safetydb API and yield VendorData with specified CVE
90+
91+
Parameters:
92+
response: A JSON object containing the response data from the safetydb datasource.
93+
94+
Yields:
95+
VendorData instance containing the advisory information for the package.
96+
"""
97+
98+
for package, advisories in response.items():
99+
if package == "$meta":
100+
continue
101+
102+
for advisory in advisories:
103+
if advisory.get("cve") == cve:
104+
yield VendorData(
105+
purl=PackageURL(type="pypi", name=package),
106+
aliases=[advisory.get("cve"), advisory.get("id")],
107+
affected_versions=sorted(advisory.get("specs")),
108+
fixed_versions=[],
109+
)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"$meta": {
3+
"advisory": "PyUp.io metadata",
4+
"base_domain": "https://pyup.io",
5+
"timestamp": 1714543250
6+
},
7+
"flask": [
8+
{
9+
"advisory": "flask version Before 0.12.3 contains a CWE-20: Improper Input Validation vulnerability in flask that can result in Large amount of memory usage possibly leading to denial of service. This attack appear to be exploitable via Attacker provides JSON data in incorrect encoding. This vulnerability appears to have been fixed in 0.12.3.",
10+
"cve": "CVE-2018-1000656",
11+
"id": "pyup.io-36388",
12+
"more_info_path": "/vulnerabilities/CVE-2018-1000656/36388",
13+
"specs": [
14+
"<0.12.3"
15+
],
16+
"v": "<0.12.3"
17+
},
18+
{
19+
"advisory": "Flask 0.12.3 includes a fix for CVE-2019-1010083: Unexpected memory usage. The impact is denial of service. The attack vector is crafted encoded JSON data. NOTE: this may overlap CVE-2018-1000656.\r\nhttps://github.com/pallets/flask/pull/2695/commits/0e1e9a04aaf29ab78f721cfc79ac2a691f6e3929",
20+
"cve": "CVE-2019-1010083",
21+
"id": "pyup.io-38654",
22+
"more_info_path": "/vulnerabilities/CVE-2019-1010083/38654",
23+
"specs": [
24+
"<0.12.3"
25+
],
26+
"v": "<0.12.3"
27+
},
28+
{
29+
"advisory": "flask 0.6.1 fixes a security problem that allowed clients to download arbitrary files if the host server was a windows based operating system and the client uses backslashes to escape the directory the files where exposed from.\r\nhttps://data.safetycli.com/vulnerabilities/PVE-2021-25820/25820/",
30+
"cve": "PVE-2021-25820",
31+
"id": "pyup.io-25820",
32+
"more_info_path": "/vulnerabilities/PVE-2021-25820/25820",
33+
"specs": [
34+
"<0.6.1"
35+
],
36+
"v": "<0.6.1"
37+
},
38+
{
39+
"advisory": "Flask 2.2.5 and 2.3.2 include a fix for CVE-2023-30861: When all of the following conditions are met, a response containing data intended for one client may be cached and subsequently sent by the proxy to other clients. If the proxy also caches 'Set-Cookie' headers, it may send one client's 'session' cookie to other clients. The severity depends on the application's use of the session and the proxy's behavior regarding cookies. The risk depends on all these conditions being met:\r\n1. The application must be hosted behind a caching proxy that does not strip cookies or ignore responses with cookies.\r\n2. The application sets 'session.permanent = True'\r\n3. The application does not access or modify the session at any point during a request.\r\n4. 'SESSION_REFRESH_EACH_REQUEST' enabled (the default).\r\n5. The application does not set a 'Cache-Control' header to indicate that a page is private or should not be cached.\r\nThis happens because vulnerable versions of Flask only set the 'Vary: Cookie' header when the session is accessed or modified, not when it is refreshed (re-sent to update the expiration) without being accessed or modified.\r\nhttps://github.com/pallets/flask/security/advisories/GHSA-m2qf-hxjv-5gpq",
40+
"cve": "CVE-2023-30861",
41+
"id": "pyup.io-55261",
42+
"more_info_path": "/vulnerabilities/CVE-2023-30861/55261",
43+
"specs": [
44+
"<2.2.5",
45+
">=2.3.0,<2.3.2"
46+
],
47+
"v": "<2.2.5,>=2.3.0,<2.3.2"
48+
}
49+
]
50+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"purl": "pkg:pypi/flask",
4+
"affected_versions": ["<0.12.3"],
5+
"fixed_versions": [],
6+
"aliases": ["CVE-2018-1000656", "pyup.io-36388"]
7+
},
8+
{
9+
"purl": "pkg:pypi/flask",
10+
"affected_versions": ["<0.12.3"],
11+
"fixed_versions": [],
12+
"aliases": ["CVE-2019-1010083", "pyup.io-38654"]
13+
},
14+
{
15+
"purl": "pkg:pypi/flask",
16+
"affected_versions": ["<0.6.1"],
17+
"fixed_versions": [],
18+
"aliases": ["PVE-2021-25820", "pyup.io-25820"]
19+
},
20+
{
21+
"purl": "pkg:pypi/flask",
22+
"affected_versions": ["<2.2.5", ">=2.3.0,<2.3.2"],
23+
"fixed_versions": [],
24+
"aliases": ["CVE-2023-30861", "pyup.io-55261"]
25+
}
26+
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"purl": "pkg:pypi/flask",
4+
"affected_versions": ["<0.12.3"],
5+
"fixed_versions": [],
6+
"aliases": ["CVE-2019-1010083", "pyup.io-38654"]
7+
}
8+
]

vulntotal/tests/test_safetydb.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/nexB/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import json
11+
from pathlib import Path
12+
13+
from commoncode import testcase
14+
from packageurl import PackageURL
15+
16+
from vulnerabilities.tests import util_tests
17+
from vulntotal.datasources import safetydb
18+
19+
20+
class TestSafetydb(testcase.FileBasedTesting):
21+
test_data_dir = str(Path(__file__).resolve().parent / "test_data" / "safetydb")
22+
23+
def test_parse_advisory(self):
24+
purl = PackageURL.from_string("pkg:pypi/flask")
25+
advisory_file = self.get_test_loc("advisory.json")
26+
with open(advisory_file) as f:
27+
advisory = json.load(f)
28+
29+
results = [adv.to_dict() for adv in safetydb.parse_advisory(advisory, purl)]
30+
expected_file = self.get_test_loc("parse_advisory-expected.json", must_exist=False)
31+
util_tests.check_results_against_json(results, expected_file)
32+
33+
def test_parse_advisory_for_cve(self):
34+
cve = "CVE-2019-1010083"
35+
advisory_file = self.get_test_loc("advisory.json")
36+
with open(advisory_file) as f:
37+
advisory = json.load(f)
38+
39+
results = [adv.to_dict() for adv in safetydb.parse_advisory_for_cve(advisory, cve)]
40+
expected_file = self.get_test_loc("parse_advisory_cve-expected.json", must_exist=False)
41+
util_tests.check_results_against_json(results, expected_file)

0 commit comments

Comments
 (0)