diff --git a/bandit/core/issue.py b/bandit/core/issue.py index 2ef6763c2..20e43310f 100644 --- a/bandit/core/issue.py +++ b/bandit/core/issue.py @@ -25,6 +25,7 @@ class Cwe: BROKEN_CRYPTO = 327 INSUFFICIENT_RANDOM_VALUES = 330 INSECURE_TEMP_FILE = 377 + UNCONTROLLED_RESOURCE_CONSUMPTION = 400 DESERIALIZATION_OF_UNTRUSTED_DATA = 502 MULTIPLE_BINDS = 605 IMPROPER_CHECK_OF_EXCEPT_COND = 703 diff --git a/bandit/plugins/request_without_timeout.py b/bandit/plugins/request_without_timeout.py new file mode 100644 index 000000000..d39fd8fae --- /dev/null +++ b/bandit/plugins/request_without_timeout.py @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: Apache-2.0 +r""" +======================================= +B113: Test for missing requests timeout +======================================= + +This plugin test checks for ``requests`` calls without a timeout specified. + +Nearly all production code should use this parameter in nearly all requests, +Failure to do so can cause your program to hang indefinitely. + +When request methods are used without the timeout parameter set, +Bandit will return a MEDIUM severity error. + + +:Example: + +.. code-block:: none + + >> Issue: [B113:request_without_timeout] Requests call without timeout + Severity: Medium Confidence: Low + CWE: CWE-400 (https://cwe.mitre.org/data/definitions/400.html) + More Info: https://bandit.readthedocs.io/en/latest/plugins/b113_request_without_timeout.html + Location: examples/requests-missing-timeout.py:3:0 + 2 + 3 requests.get('https://gmail.com') + 4 requests.get('https://gmail.com', timeout=None) + + -------------------------------------------------- + >> Issue: [B113:request_without_timeout] Requests call with timeout set to None + Severity: Medium Confidence: Low + CWE: CWE-400 (https://cwe.mitre.org/data/definitions/400.html) + More Info: https://bandit.readthedocs.io/en/latest/plugins/b113_request_without_timeout.html + Location: examples/requests-missing-timeout.py:4:0 + 3 requests.get('https://gmail.com') + 4 requests.get('https://gmail.com', timeout=None) + 5 requests.get('https://gmail.com', timeout=5) + +.. seealso:: + + - https://2.python-requests.org/en/master/user/quickstart/#timeouts + +.. versionadded:: 1.7.5 + +""" # noqa: E501 +import bandit +from bandit.core import issue +from bandit.core import test_properties as test + + +@test.checks("Call") +@test.test_id("B113") +def request_without_timeout(context): + http_verbs = ("get", "options", "head", "post", "put", "patch", "delete") + if ( + "requests" in context.call_function_name_qual + and context.call_function_name in http_verbs + ): + # check for missing timeout + if context.check_call_arg_value("timeout") is None: + return bandit.Issue( + severity=bandit.MEDIUM, + confidence=bandit.LOW, + cwe=issue.Cwe.UNCONTROLLED_RESOURCE_CONSUMPTION, + text="Requests call without timeout", + ) + # check for timeout=None + if context.check_call_arg_value("timeout", "None"): + return bandit.Issue( + severity=bandit.MEDIUM, + confidence=bandit.LOW, + cwe=issue.Cwe.UNCONTROLLED_RESOURCE_CONSUMPTION, + text="Requests call with timeout set to None", + ) diff --git a/doc/source/plugins/b113_request_without_timeout.rst b/doc/source/plugins/b113_request_without_timeout.rst new file mode 100644 index 000000000..42a5f2510 --- /dev/null +++ b/doc/source/plugins/b113_request_without_timeout.rst @@ -0,0 +1,5 @@ +----------------------------- +B113: request_without_timeout +----------------------------- + +.. automodule:: bandit.plugins.request_without_timeout diff --git a/examples/httpoxy_cgihandler.py b/examples/httpoxy_cgihandler.py index aa9e165f2..b091c29bf 100644 --- a/examples/httpoxy_cgihandler.py +++ b/examples/httpoxy_cgihandler.py @@ -2,7 +2,7 @@ import wsgiref.handlers def application(environ, start_response): - r = requests.get('https://192.168.0.42/private/api/foobar') + r = requests.get('https://192.168.0.42/private/api/foobar', timeout=30) start_response('200 OK', [('Content-Type', 'text/plain')]) return [r.content] diff --git a/examples/requests-missing-timeout.py b/examples/requests-missing-timeout.py new file mode 100644 index 000000000..75cb5a7ff --- /dev/null +++ b/examples/requests-missing-timeout.py @@ -0,0 +1,23 @@ +import requests + +requests.get('https://gmail.com') +requests.get('https://gmail.com', timeout=None) +requests.get('https://gmail.com', timeout=5) +requests.post('https://gmail.com') +requests.post('https://gmail.com', timeout=None) +requests.post('https://gmail.com', timeout=5) +requests.put('https://gmail.com') +requests.put('https://gmail.com', timeout=None) +requests.put('https://gmail.com', timeout=5) +requests.delete('https://gmail.com') +requests.delete('https://gmail.com', timeout=None) +requests.delete('https://gmail.com', timeout=5) +requests.patch('https://gmail.com') +requests.patch('https://gmail.com', timeout=None) +requests.patch('https://gmail.com', timeout=5) +requests.options('https://gmail.com') +requests.options('https://gmail.com', timeout=None) +requests.options('https://gmail.com', timeout=5) +requests.head('https://gmail.com') +requests.head('https://gmail.com', timeout=None) +requests.head('https://gmail.com', timeout=5) diff --git a/examples/requests-ssl-verify-disabled.py b/examples/requests-ssl-verify-disabled.py index e314036bf..25f5ef41f 100644 --- a/examples/requests-ssl-verify-disabled.py +++ b/examples/requests-ssl-verify-disabled.py @@ -1,21 +1,20 @@ import httpx import requests - -requests.get('https://gmail.com', verify=True) -requests.get('https://gmail.com', verify=False) -requests.post('https://gmail.com', verify=True) -requests.post('https://gmail.com', verify=False) -requests.put('https://gmail.com', verify=True) -requests.put('https://gmail.com', verify=False) -requests.delete('https://gmail.com', verify=True) -requests.delete('https://gmail.com', verify=False) -requests.patch('https://gmail.com', verify=True) -requests.patch('https://gmail.com', verify=False) -requests.options('https://gmail.com', verify=True) -requests.options('https://gmail.com', verify=False) -requests.head('https://gmail.com', verify=True) -requests.head('https://gmail.com', verify=False) +requests.get('https://gmail.com', timeout=30, verify=True) +requests.get('https://gmail.com', timeout=30, verify=False) +requests.post('https://gmail.com', timeout=30, verify=True) +requests.post('https://gmail.com', timeout=30, verify=False) +requests.put('https://gmail.com', timeout=30, verify=True) +requests.put('https://gmail.com', timeout=30, verify=False) +requests.delete('https://gmail.com', timeout=30, verify=True) +requests.delete('https://gmail.com', timeout=30, verify=False) +requests.patch('https://gmail.com', timeout=30, verify=True) +requests.patch('https://gmail.com', timeout=30, verify=False) +requests.options('https://gmail.com', timeout=30, verify=True) +requests.options('https://gmail.com', timeout=30, verify=False) +requests.head('https://gmail.com', timeout=30, verify=True) +requests.head('https://gmail.com', timeout=30, verify=False) httpx.request('GET', 'https://gmail.com', verify=True) httpx.request('GET', 'https://gmail.com', verify=False) diff --git a/setup.cfg b/setup.cfg index 7449f15bf..9b347306e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,9 @@ bandit.plugins = # bandit/plugins/crypto_request_no_cert_validation.py request_with_no_cert_validation = bandit.plugins.crypto_request_no_cert_validation:request_with_no_cert_validation + # bandit/plugins/request_without_timeout.py + request_without_timeout = bandit.plugins.request_without_timeout:request_without_timeout + # bandit/plugins/exec.py exec_used = bandit.plugins.exec:exec_used diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 29eb9b9cc..adad9f562 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -393,6 +393,14 @@ def test_requests_ssl_verify_disabled(self): } self.check_example("requests-ssl-verify-disabled.py", expect) + def test_requests_without_timeout(self): + """Test for the `requests` library missing timeouts.""" + expect = { + "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 14, "HIGH": 0}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 14, "MEDIUM": 0, "HIGH": 0}, + } + self.check_example("requests-missing-timeout.py", expect) + def test_skip(self): """Test `#nosec` and `#noqa` comments.""" expect = {