From 69c3ade168d0199883f2eec3c1db118f2e1c0c1c Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Wed, 9 Feb 2022 21:50:00 -0800 Subject: [PATCH 1/2] Better hashlib check for Python 3.9 In Python 3.9 and later, the hashlib function has a new keyword argument usedforsecurity to describe the usage of the hash. In that way, we can better identify the severity of the error. Previously, hashlib.md5 and the like were part of the blacklist check. For Python 3.9, it'll be part of the hashlib plugin so it can do more advanced checking of usedforsecurity. Signed-off-by: Eric Brown --- bandit/blacklists/calls.py | 68 ++++++---- bandit/plugins/hashlib_insecure_functions.py | 118 ++++++++++++++++++ .../plugins/hashlib_new_insecure_functions.py | 85 ------------- examples/crypto-md5.py | 2 + examples/hashlib_new_insecure_functions.py | 6 - setup.cfg | 4 +- tests/functional/test_functional.py | 66 ++++++++-- 7 files changed, 223 insertions(+), 126 deletions(-) create mode 100644 bandit/plugins/hashlib_insecure_functions.py delete mode 100644 bandit/plugins/hashlib_new_insecure_functions.py 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..c279082ba --- /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 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) From 0b542fb6d3ad73f6bea31aacc72fd41653c35834 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Thu, 10 Feb 2022 08:27:29 -0800 Subject: [PATCH 2/2] Update hashlib_insecure_functions.py --- bandit/plugins/hashlib_insecure_functions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bandit/plugins/hashlib_insecure_functions.py b/bandit/plugins/hashlib_insecure_functions.py index c279082ba..31c18b114 100644 --- a/bandit/plugins/hashlib_insecure_functions.py +++ b/bandit/plugins/hashlib_insecure_functions.py @@ -11,11 +11,11 @@ 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 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. +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: