diff --git a/bandit/plugins/crypto_request_no_cert_validation.py b/bandit/plugins/crypto_request_no_cert_validation.py index 11791ed1e..a9b44e0f0 100644 --- a/bandit/plugins/crypto_request_no_cert_validation.py +++ b/bandit/plugins/crypto_request_no_cert_validation.py @@ -45,6 +45,9 @@ .. versionchanged:: 1.7.5 Added check for httpx module +.. versionchanged:: 1.9.5 + Added check for requests.Session and httpx.Client instance method calls + """ import bandit from bandit.core import issue @@ -58,6 +61,7 @@ def request_with_no_cert_validation(context): HTTPX_ATTRS = {"request", "stream", "Client", "AsyncClient"} | HTTP_VERBS qualname = context.call_function_name_qual.split(".")[0] + # Check module-level calls: requests.get, httpx.get, etc. if ( qualname == "requests" and context.call_function_name in HTTP_VERBS @@ -73,3 +77,24 @@ def request_with_no_cert_validation(context): "certificate checks, security issue.", lineno=context.get_lineno_for_call_arg("verify"), ) + + # Check instance method calls: session.get, client.get, etc. + # When requests/httpx is imported and an HTTP verb is called with + # verify=False on any object, it's likely a Session/Client method. + if ( + context.call_function_name in HTTP_VERBS + and context.check_call_arg_value("verify", "False") + ): + if context.is_module_imported_exact( + "requests" + ) or context.is_module_imported_exact("httpx"): + return bandit.Issue( + severity=bandit.HIGH, + confidence=bandit.MEDIUM, + cwe=issue.Cwe.IMPROPER_CERT_VALIDATION, + text="Call to {func} with verify=False disabling SSL " + "certificate checks, security issue.".format( + func=context.call_function_name_qual + ), + lineno=context.get_lineno_for_call_arg("verify"), + ) diff --git a/examples/requests-ssl-verify-disabled.py b/examples/requests-ssl-verify-disabled.py index c45b9e944..e5976f13b 100644 --- a/examples/requests-ssl-verify-disabled.py +++ b/examples/requests-ssl-verify-disabled.py @@ -40,3 +40,22 @@ httpx.Client(timeout=30, verify=False) httpx.AsyncClient(timeout=30) httpx.AsyncClient(timeout=30, verify=False) + +# Instance method calls (requests.Session) +session = requests.Session() +session.get('https://gmail.com', timeout=30, verify=True) +session.get('https://gmail.com', timeout=30, verify=False) +session.post('https://gmail.com', timeout=30, verify=True) +session.post('https://gmail.com', timeout=30, verify=False) +with requests.Session() as scoped_session: + scoped_session.put('https://gmail.com', timeout=30, verify=True) + scoped_session.put('https://gmail.com', timeout=30, verify=False) + scoped_session.delete('https://gmail.com', timeout=30, verify=True) + scoped_session.delete('https://gmail.com', timeout=30, verify=False) + +# Instance method calls (httpx.Client) +client = httpx.Client(timeout=30) +client.get('https://gmail.com', timeout=30, verify=True) +client.get('https://gmail.com', timeout=30, verify=False) +client.post('https://gmail.com', timeout=30, verify=True) +client.post('https://gmail.com', timeout=30, verify=False) \ No newline at end of file diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 08b1c5c5b..d1368eb49 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -373,8 +373,8 @@ def test_random_module(self): def test_requests_ssl_verify_disabled(self): """Test for the `requests` library skipping verification.""" expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 18}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 18}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 24}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 6, "HIGH": 18}, } self.check_example("requests-ssl-verify-disabled.py", expect)