|
6 | 6 | ====================================================================== |
7 | 7 |
|
8 | 8 | This plugin checks for the usage of the insecure MD4, MD5, or SHA1 hash |
9 | | -functions in ``hashlib``. The ``hashlib.new`` function provides |
| 9 | +functions in ``hashlib`` and ``crypt``. The ``hashlib.new`` function provides |
10 | 10 | the ability to construct a new hashing object using the named algorithm. This |
11 | 11 | can be used to create insecure hash functions like MD4 and MD5 if they are |
12 | 12 | passed as algorithm names to this function. |
|
17 | 17 | does additional checking for usage of keyword usedforsecurity on all |
18 | 18 | function variations of hashlib. |
19 | 19 |
|
| 20 | +Similar to ``hashlib``, this plugin also checks for usage of one of the |
| 21 | +``crypt`` module's weak hashes. ``crypt`` also permits MD5 among other weak |
| 22 | +hash variants. |
| 23 | +
|
20 | 24 | :Example: |
21 | 25 |
|
22 | 26 | .. code-block:: none |
|
40 | 44 | .. versionchanged:: 1.7.3 |
41 | 45 | CWE information added |
42 | 46 |
|
| 47 | +.. versionchanged:: 1.7.6 |
| 48 | + Added check for the crypt module weak hashes |
| 49 | +
|
43 | 50 | """ # noqa: E501 |
44 | 51 | import sys |
45 | 52 |
|
|
48 | 55 | from bandit.core import test_properties as test |
49 | 56 |
|
50 | 57 | WEAK_HASHES = ("md4", "md5", "sha", "sha1") |
51 | | - |
52 | | - |
53 | | -def _hashlib_func(context): |
54 | | - if isinstance(context.call_function_name_qual, str): |
55 | | - qualname_list = context.call_function_name_qual.split(".") |
56 | | - |
57 | | - if "hashlib" in qualname_list: |
58 | | - func = qualname_list[-1] |
59 | | - keywords = context.call_keywords |
60 | | - |
61 | | - if func in WEAK_HASHES: |
62 | | - if keywords.get("usedforsecurity", "True") == "True": |
63 | | - return bandit.Issue( |
64 | | - severity=bandit.HIGH, |
65 | | - confidence=bandit.HIGH, |
66 | | - cwe=issue.Cwe.BROKEN_CRYPTO, |
67 | | - text=f"Use of weak {func.upper()} hash for security. " |
68 | | - "Consider usedforsecurity=False", |
69 | | - lineno=context.node.lineno, |
70 | | - ) |
71 | | - elif func == "new": |
72 | | - args = context.call_args |
73 | | - name = args[0] if args else keywords.get("name", None) |
74 | | - if isinstance(name, str) and name.lower() in WEAK_HASHES: |
75 | | - if keywords.get("usedforsecurity", "True") == "True": |
76 | | - return bandit.Issue( |
77 | | - severity=bandit.HIGH, |
78 | | - confidence=bandit.HIGH, |
79 | | - cwe=issue.Cwe.BROKEN_CRYPTO, |
80 | | - text=f"Use of weak {name.upper()} hash for " |
81 | | - "security. Consider usedforsecurity=False", |
82 | | - lineno=context.node.lineno, |
83 | | - ) |
84 | | - |
85 | | - |
86 | | -def _hashlib_new(context): |
87 | | - if isinstance(context.call_function_name_qual, str): |
88 | | - qualname_list = context.call_function_name_qual.split(".") |
89 | | - func = qualname_list[-1] |
90 | | - |
91 | | - if "hashlib" in qualname_list and func == "new": |
92 | | - args = context.call_args |
93 | | - keywords = context.call_keywords |
94 | | - name = args[0] if args else keywords.get("name", None) |
95 | | - if isinstance(name, str) and name.lower() in WEAK_HASHES: |
| 58 | +WEAK_CRYPT_HASHES = ("METHOD_CRYPT", "METHOD_MD5", "METHOD_BLOWFISH") |
| 59 | + |
| 60 | + |
| 61 | +def _hashlib_func(context, func): |
| 62 | + keywords = context.call_keywords |
| 63 | + |
| 64 | + if func in WEAK_HASHES: |
| 65 | + if keywords.get("usedforsecurity", "True") == "True": |
| 66 | + return bandit.Issue( |
| 67 | + severity=bandit.HIGH, |
| 68 | + confidence=bandit.HIGH, |
| 69 | + cwe=issue.Cwe.BROKEN_CRYPTO, |
| 70 | + text=f"Use of weak {func.upper()} hash for security. " |
| 71 | + "Consider usedforsecurity=False", |
| 72 | + lineno=context.node.lineno, |
| 73 | + ) |
| 74 | + elif func == "new": |
| 75 | + args = context.call_args |
| 76 | + name = args[0] if args else keywords.get("name", None) |
| 77 | + if isinstance(name, str) and name.lower() in WEAK_HASHES: |
| 78 | + if keywords.get("usedforsecurity", "True") == "True": |
96 | 79 | return bandit.Issue( |
97 | | - severity=bandit.MEDIUM, |
| 80 | + severity=bandit.HIGH, |
98 | 81 | confidence=bandit.HIGH, |
99 | 82 | cwe=issue.Cwe.BROKEN_CRYPTO, |
100 | | - text=f"Use of insecure {name.upper()} hash function.", |
| 83 | + text=f"Use of weak {name.upper()} hash for " |
| 84 | + "security. Consider usedforsecurity=False", |
101 | 85 | lineno=context.node.lineno, |
102 | 86 | ) |
103 | 87 |
|
104 | 88 |
|
| 89 | +def _hashlib_new(context, func): |
| 90 | + if func == "new": |
| 91 | + args = context.call_args |
| 92 | + keywords = context.call_keywords |
| 93 | + name = args[0] if args else keywords.get("name", None) |
| 94 | + if isinstance(name, str) and name.lower() in WEAK_HASHES: |
| 95 | + return bandit.Issue( |
| 96 | + severity=bandit.MEDIUM, |
| 97 | + confidence=bandit.HIGH, |
| 98 | + cwe=issue.Cwe.BROKEN_CRYPTO, |
| 99 | + text=f"Use of insecure {name.upper()} hash function.", |
| 100 | + lineno=context.node.lineno, |
| 101 | + ) |
| 102 | + |
| 103 | + |
| 104 | +def _crypt_crypt(context, func): |
| 105 | + args = context.call_args |
| 106 | + keywords = context.call_keywords |
| 107 | + |
| 108 | + if func == "crypt": |
| 109 | + name = args[1] if len(args) > 1 else keywords.get("salt", None) |
| 110 | + if isinstance(name, str) and name in WEAK_CRYPT_HASHES: |
| 111 | + return bandit.Issue( |
| 112 | + severity=bandit.MEDIUM, |
| 113 | + confidence=bandit.HIGH, |
| 114 | + cwe=issue.Cwe.BROKEN_CRYPTO, |
| 115 | + text=f"Use of insecure crypt.{name.upper()} hash function.", |
| 116 | + lineno=context.node.lineno, |
| 117 | + ) |
| 118 | + elif func == "mksalt": |
| 119 | + name = args[0] if args else keywords.get("method", None) |
| 120 | + if isinstance(name, str) and name in WEAK_CRYPT_HASHES: |
| 121 | + return bandit.Issue( |
| 122 | + severity=bandit.MEDIUM, |
| 123 | + confidence=bandit.HIGH, |
| 124 | + cwe=issue.Cwe.BROKEN_CRYPTO, |
| 125 | + text=f"Use of insecure crypt.{name.upper()} hash function.", |
| 126 | + lineno=context.node.lineno, |
| 127 | + ) |
| 128 | + |
| 129 | + |
105 | 130 | @test.test_id("B324") |
106 | 131 | @test.checks("Call") |
107 | 132 | def hashlib(context): |
108 | | - if sys.version_info >= (3, 9): |
109 | | - return _hashlib_func(context) |
110 | | - else: |
111 | | - return _hashlib_new(context) |
| 133 | + if isinstance(context.call_function_name_qual, str): |
| 134 | + qualname_list = context.call_function_name_qual.split(".") |
| 135 | + func = qualname_list[-1] |
| 136 | + |
| 137 | + if "hashlib" in qualname_list: |
| 138 | + if sys.version_info >= (3, 9): |
| 139 | + return _hashlib_func(context, func) |
| 140 | + else: |
| 141 | + return _hashlib_new(context, func) |
| 142 | + |
| 143 | + elif "crypt" in qualname_list and func in ("crypt", "mksalt"): |
| 144 | + return _crypt_crypt(context, func) |
0 commit comments