diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py index 480f79bb8..65e471d86 100644 --- a/bandit/blacklists/calls.py +++ b/bandit/blacklists/calls.py @@ -313,6 +313,8 @@ +------+---------------------+------------------------------------+-----------+ """ +import sys + from bandit.blacklists import utils from bandit.core import issue @@ -362,28 +364,52 @@ def gen_blacklist(): ) ) - sets.append( - utils.build_conf_dict( - "md5", - "B303", - issue.Cwe.BROKEN_CRYPTO, - [ - "hashlib.md5", - "hashlib.sha1", - "Crypto.Hash.MD2.new", - "Crypto.Hash.MD4.new", - "Crypto.Hash.MD5.new", - "Crypto.Hash.SHA.new", - "Cryptodome.Hash.MD2.new", - "Cryptodome.Hash.MD4.new", - "Cryptodome.Hash.MD5.new", - "Cryptodome.Hash.SHA.new", - "cryptography.hazmat.primitives.hashes.MD5", - "cryptography.hazmat.primitives.hashes.SHA1", - ], - "Use of insecure MD2, MD4, MD5, or SHA1 hash function.", + if sys.version_info >= (3, 9): + sets.append( + utils.build_conf_dict( + "md5", + "B303", + issue.Cwe.BROKEN_CRYPTO, + [ + "Crypto.Hash.MD2.new", + "Crypto.Hash.MD4.new", + "Crypto.Hash.MD5.new", + "Crypto.Hash.SHA.new", + "Cryptodome.Hash.MD2.new", + "Cryptodome.Hash.MD4.new", + "Cryptodome.Hash.MD5.new", + "Cryptodome.Hash.SHA.new", + "cryptography.hazmat.primitives.hashes.MD5", + "cryptography.hazmat.primitives.hashes.SHA1", + ], + "Use of insecure MD2, MD4, MD5, or SHA1 hash function.", + ) + ) + else: + sets.append( + utils.build_conf_dict( + "md5", + "B303", + issue.Cwe.BROKEN_CRYPTO, + [ + "hashlib.md4", + "hashlib.md5", + "hashlib.sha", + "hashlib.sha1", + "Crypto.Hash.MD2.new", + "Crypto.Hash.MD4.new", + "Crypto.Hash.MD5.new", + "Crypto.Hash.SHA.new", + "Cryptodome.Hash.MD2.new", + "Cryptodome.Hash.MD4.new", + "Cryptodome.Hash.MD5.new", + "Cryptodome.Hash.SHA.new", + "cryptography.hazmat.primitives.hashes.MD5", + "cryptography.hazmat.primitives.hashes.SHA1", + ], + "Use of insecure MD2, MD4, MD5, or SHA1 hash function.", + ) ) - ) sets.append( utils.build_conf_dict( diff --git a/bandit/plugins/hashlib_insecure_functions.py b/bandit/plugins/hashlib_insecure_functions.py new file mode 100644 index 000000000..31c18b114 --- /dev/null +++ b/bandit/plugins/hashlib_insecure_functions.py @@ -0,0 +1,118 @@ +# +# SPDX-License-Identifier: Apache-2.0 +r""" +====================================================================== +B324: Test use of insecure md4, md5, or sha1 hash functions in hashlib +====================================================================== + +This plugin checks for the usage of the insecure MD4, MD5, or SHA1 hash +functions in ``hashlib``. The ``hashlib.new`` function provides +the ability to construct a new hashing object using the named algorithm. This +can be used to create insecure hash functions like MD4 and MD5 if they are +passed as algorithm names to this function. + +For Python versions prior to 3.9, this check is similar to B303 blacklist +except that this checks for insecure hash functions created using +``hashlib.new`` function. For Python version 3.9 and later, this check +does additional checking for usage of keyword usedforsecurity on all +function variations of hashlib. + +:Example: + + >> Issue: [B324:hashlib] Use of weak MD2, MD4, MD5, or SHA1 hash for + security. Consider usedforsecurity=False + Severity: High Confidence: High + CWE: CWE-327 (https://cwe.mitre.org/data/definitions/327.html) + Location: examples/hashlib_new_insecure_functions.py:3:0 + More Info: https://bandit.readthedocs.io/en/latest/plugins/b324_hashlib.html + 2 + 3 hashlib.new('md5') + 4 + +.. seealso:: + + - https://cwe.mitre.org/data/definitions/327.html + +.. versionadded:: 1.5.0 + +.. versionchanged:: 1.7.3 + CWE information added + +""" # noqa: E501 +import sys + +import bandit +from bandit.core import issue +from bandit.core import test_properties as test + + +def _hashlib_func(context): + if isinstance(context.call_function_name_qual, str): + qualname_list = context.call_function_name_qual.split(".") + + if "hashlib" in qualname_list: + func = qualname_list[-1] + args = context.call_args + keywords = context.call_keywords + name = args[0] if args else keywords["name"] + + if func in ("md4", "md5", "sha", "sha1"): + if keywords.get("usedforsecurity", "True") == "True": + return bandit.Issue( + severity=bandit.HIGH, + confidence=bandit.HIGH, + cwe=issue.Cwe.BROKEN_CRYPTO, + text="Use of weak MD2, MD4, MD5, or SHA1 hash " + "for security. Consider usedforsecurity=False", + lineno=context.node.lineno, + ) + elif func == "new": + if isinstance(name, str) and name.lower() in ( + "md4", + "md5", + "sha", + "sha1", + ): + if keywords.get("usedforsecurity", "True") == "True": + return bandit.Issue( + severity=bandit.HIGH, + confidence=bandit.HIGH, + cwe=issue.Cwe.BROKEN_CRYPTO, + text="Use of weak MD2, MD4, MD5, or SHA1 hash " + "for security. Consider usedforsecurity=False", + lineno=context.node.lineno, + ) + + +def _hashlib_new(context): + if isinstance(context.call_function_name_qual, str): + qualname_list = context.call_function_name_qual.split(".") + func = qualname_list[-1] + + if "hashlib" in qualname_list and func == "new": + args = context.call_args + keywords = context.call_keywords + name = args[0] if args else keywords["name"] + if isinstance(name, str) and name.lower() in ( + "md4", + "md5", + "sha", + "sha1", + ): + return bandit.Issue( + severity=bandit.MEDIUM, + confidence=bandit.HIGH, + cwe=issue.Cwe.BROKEN_CRYPTO, + text="Use of insecure MD2, MD4, MD5, or SHA1 hash " + "function.", + lineno=context.node.lineno, + ) + + +@test.test_id("B324") +@test.checks("Call") +def hashlib(context): + if sys.version_info >= (3, 9): + return _hashlib_func(context) + else: + return _hashlib_new(context) diff --git a/bandit/plugins/hashlib_new_insecure_functions.py b/bandit/plugins/hashlib_new_insecure_functions.py deleted file mode 100644 index 302a028f1..000000000 --- a/bandit/plugins/hashlib_new_insecure_functions.py +++ /dev/null @@ -1,85 +0,0 @@ -# -# SPDX-License-Identifier: Apache-2.0 -r""" -============================================================================ -B324: Test use of insecure md4, md5, or sha1 hash functions in hashlib.new() -============================================================================ - -This plugin checks for the usage of the insecure MD4, MD5, or SHA1 hash -functions in ``hashlib.new`` function. The ``hashlib.new`` function provides -the ability to construct a new hashing object using the named algorithm. This -can be used to create insecure hash functions like MD4 and MD5 if they are -passed as algorithm names to this function. - -This is similar to B303 blacklist check, except that this checks for insecure -hash functions created using ``hashlib.new`` function. - -:Example: - - >> Issue: [B324:hashlib_new] Use of insecure MD4 or MD5 hash function. - Severity: Medium Confidence: High - CWE: CWE-327 (https://cwe.mitre.org/data/definitions/327.html) - Location: examples/hashlib_new_insecure_funcs.py:3 - 2 - 3 md5_hash = hashlib.new('md5', string='test') - 4 print(md5_hash) - -.. seealso:: - - - https://cwe.mitre.org/data/definitions/327.html - -.. versionadded:: 1.5.0 - -.. versionchanged:: 1.7.3 - CWE information added - -""" -import sys - -import bandit -from bandit.core import issue -from bandit.core import test_properties as test - - -@test.test_id("B324") -@test.checks("Call") -def hashlib_new(context): - if isinstance(context.call_function_name_qual, str): - qualname_list = context.call_function_name_qual.split(".") - func = qualname_list[-1] - if "hashlib" in qualname_list and func == "new": - args = context.call_args - keywords = context.call_keywords - name = args[0] if args else keywords["name"] - if isinstance(name, str) and name.lower() in ( - "md4", - "md5", - "sha", - "sha1", - ): - if sys.version_info >= (3, 9): - # Python 3.9 includes a usedforsecurity argument - usedforsecurity = ( - args[2] - if len(args) > 2 - else keywords.get("usedforsecurity", "True") - ) - - if usedforsecurity == "True": - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - cwe=issue.Cwe.BROKEN_CRYPTO, - text="Use of insecure MD2, MD4, MD5, or SHA1 hash " - "function.", - lineno=context.node.lineno, - ) - else: - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.HIGH, - cwe=issue.Cwe.BROKEN_CRYPTO, - text="Use of insecure MD2, MD4, MD5, or SHA1 hash " - "function.", - lineno=context.node.lineno, - ) diff --git a/examples/crypto-md5.py b/examples/crypto-md5.py index 045740c3b..b78fd4c82 100644 --- a/examples/crypto-md5.py +++ b/examples/crypto-md5.py @@ -18,6 +18,8 @@ hashlib.sha1(1) +hashlib.sha1(usedforsecurity=False) + pycrypto_md2.new() pycrypto_md4.new() pycrypto_md5.new() diff --git a/examples/hashlib_new_insecure_functions.py b/examples/hashlib_new_insecure_functions.py index 96a774ad9..914b3056d 100644 --- a/examples/hashlib_new_insecure_functions.py +++ b/examples/hashlib_new_insecure_functions.py @@ -10,9 +10,6 @@ hashlib.new(string='test', name='MD5') -# 3rd arg only availabe in Python 3.9+ -hashlib.new('md5', b'test', True) - hashlib.new('sha1') hashlib.new(string='test', name='SHA1') @@ -29,8 +26,5 @@ hashlib.new('SHA512') -# 3rd arg only availabe in Python 3.9+ -hashlib.new('md5', b'test', False) - # usedforsecurity arg only availabe in Python 3.9+ hashlib.new(name='sha1', usedforsecurity=False) diff --git a/setup.cfg b/setup.cfg index 5a2ef37b8..6f960c5da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -90,8 +90,8 @@ bandit.plugins = # bandit/plugins/injection_sql.py hardcoded_sql_expressions = bandit.plugins.injection_sql:hardcoded_sql_expressions - # bandit/plugins/hashlib_new_insecure_functions.py - hashlib_new_insecure_functions = bandit.plugins.hashlib_new_insecure_functions:hashlib_new + # bandit/plugins/hashlib_insecure_functions.py + hashlib_insecure_functions = bandit.plugins.hashlib_insecure_functions:hashlib # bandit/plugins/injection_wildcard.py linux_commands_wildcard_injection = bandit.plugins.injection_wildcard:linux_commands_wildcard_injection diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index ecfe8780f..1de39bac0 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -108,10 +108,36 @@ def test_binding(self): def test_crypto_md5(self): """Test the `hashlib.md5` example.""" - expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 15, "HIGH": 4}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 19}, - } + if sys.version_info >= (3, 9): + expect = { + "SEVERITY": { + "UNDEFINED": 0, + "LOW": 0, + "MEDIUM": 10, + "HIGH": 9, + }, + "CONFIDENCE": { + "UNDEFINED": 0, + "LOW": 0, + "MEDIUM": 0, + "HIGH": 19, + }, + } + else: + expect = { + "SEVERITY": { + "UNDEFINED": 0, + "LOW": 0, + "MEDIUM": 16, + "HIGH": 4, + }, + "CONFIDENCE": { + "UNDEFINED": 0, + "LOW": 0, + "MEDIUM": 0, + "HIGH": 20, + }, + } self.check_example("crypto-md5.py", expect) def test_ciphers(self): @@ -180,10 +206,26 @@ def test_httplib_https(self): def test_imports_aliases(self): """Test the `import X as Y` syntax.""" - expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 4, "MEDIUM": 5, "HIGH": 0}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 9}, - } + if sys.version_info >= (3, 9): + expect = { + "SEVERITY": {"UNDEFINED": 0, "LOW": 4, "MEDIUM": 1, "HIGH": 4}, + "CONFIDENCE": { + "UNDEFINED": 0, + "LOW": 0, + "MEDIUM": 0, + "HIGH": 9, + }, + } + else: + expect = { + "SEVERITY": {"UNDEFINED": 0, "LOW": 4, "MEDIUM": 5, "HIGH": 0}, + "CONFIDENCE": { + "UNDEFINED": 0, + "LOW": 0, + "MEDIUM": 0, + "HIGH": 9, + }, + } self.check_example("imports-aliases.py", expect) def test_imports_from(self): @@ -786,13 +828,13 @@ def test_hashlib_new_insecure_functions(self): "UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, - "HIGH": 11, + "HIGH": 10, }, "CONFIDENCE": { "UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, - "HIGH": 11, + "HIGH": 10, }, } else: @@ -800,14 +842,14 @@ def test_hashlib_new_insecure_functions(self): "SEVERITY": { "UNDEFINED": 0, "LOW": 0, - "MEDIUM": 13, + "MEDIUM": 11, "HIGH": 0, }, "CONFIDENCE": { "UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, - "HIGH": 13, + "HIGH": 11, }, } self.check_example("hashlib_new_insecure_functions.py", expect)