@@ -9,7 +9,8 @@ use crate::checkers::ast::Checker;
9
9
use super :: super :: helpers:: string_literal;
10
10
11
11
/// ## What it does
12
- /// Checks for uses of weak or broken cryptographic hash functions.
12
+ /// Checks for uses of weak or broken cryptographic hash functions in
13
+ /// `hashlib` and `crypt` libraries.
13
14
///
14
15
/// ## Why is this bad?
15
16
/// Weak or broken cryptographic hash functions may be susceptible to
@@ -43,68 +44,134 @@ use super::super::helpers::string_literal;
43
44
///
44
45
/// ## References
45
46
/// - [Python documentation: `hashlib` — Secure hashes and message digests](https://docs.python.org/3/library/hashlib.html)
47
+ /// - [Python documentation: `crypt` — Function to check Unix passwords](https://docs.python.org/3/library/crypt.html)
46
48
/// - [Common Weakness Enumeration: CWE-327](https://cwe.mitre.org/data/definitions/327.html)
47
49
/// - [Common Weakness Enumeration: CWE-328](https://cwe.mitre.org/data/definitions/328.html)
48
50
/// - [Common Weakness Enumeration: CWE-916](https://cwe.mitre.org/data/definitions/916.html)
49
51
#[ violation]
50
52
pub struct HashlibInsecureHashFunction {
53
+ library : String ,
51
54
string : String ,
52
55
}
53
56
54
57
impl Violation for HashlibInsecureHashFunction {
55
58
#[ derive_message_formats]
56
59
fn message ( & self ) -> String {
57
- let HashlibInsecureHashFunction { string } = self ;
58
- format ! ( "Probable use of insecure hash functions in `hashlib `: `{string}`" )
60
+ let HashlibInsecureHashFunction { library , string } = self ;
61
+ format ! ( "Probable use of insecure hash functions in `{library} `: `{string}`" )
59
62
}
60
63
}
61
64
62
65
/// S324
63
66
pub ( crate ) fn hashlib_insecure_hash_functions ( checker : & mut Checker , call : & ast:: ExprCall ) {
64
- if let Some ( hashlib_call ) = checker
67
+ if let Some ( weak_hash_call ) = checker
65
68
. semantic ( )
66
69
. resolve_qualified_name ( & call. func )
67
70
. and_then ( |qualified_name| match qualified_name. segments ( ) {
68
- [ "hashlib" , "new" ] => Some ( HashlibCall :: New ) ,
69
- [ "hashlib" , "md4" ] => Some ( HashlibCall :: WeakHash ( "md4" ) ) ,
70
- [ "hashlib" , "md5" ] => Some ( HashlibCall :: WeakHash ( "md5" ) ) ,
71
- [ "hashlib" , "sha" ] => Some ( HashlibCall :: WeakHash ( "sha" ) ) ,
72
- [ "hashlib" , "sha1" ] => Some ( HashlibCall :: WeakHash ( "sha1" ) ) ,
71
+ [ "hashlib" , "new" ] => Some ( WeakHashCall :: Hashlib {
72
+ call : HashlibCall :: New ,
73
+ } ) ,
74
+ [ "hashlib" , "md4" ] => Some ( WeakHashCall :: Hashlib {
75
+ call : HashlibCall :: WeakHash ( "md4" ) ,
76
+ } ) ,
77
+ [ "hashlib" , "md5" ] => Some ( WeakHashCall :: Hashlib {
78
+ call : HashlibCall :: WeakHash ( "md5" ) ,
79
+ } ) ,
80
+ [ "hashlib" , "sha" ] => Some ( WeakHashCall :: Hashlib {
81
+ call : HashlibCall :: WeakHash ( "sha" ) ,
82
+ } ) ,
83
+ [ "hashlib" , "sha1" ] => Some ( WeakHashCall :: Hashlib {
84
+ call : HashlibCall :: WeakHash ( "sha1" ) ,
85
+ } ) ,
86
+ [ "crypt" , "crypt" | "mksalt" ] => Some ( WeakHashCall :: Crypt ) ,
73
87
_ => None ,
74
88
} )
75
89
{
76
- if !is_used_for_security ( & call. arguments ) {
77
- return ;
78
- }
79
- match hashlib_call {
80
- HashlibCall :: New => {
81
- if let Some ( name_arg) = call. arguments . find_argument ( "name" , 0 ) {
82
- if let Some ( hash_func_name) = string_literal ( name_arg) {
83
- // `hashlib.new` accepts both lowercase and uppercase names for hash
84
- // functions.
85
- if matches ! (
86
- hash_func_name,
87
- "md4" | "md5" | "sha" | "sha1" | "MD4" | "MD5" | "SHA" | "SHA1"
88
- ) {
89
- checker. diagnostics . push ( Diagnostic :: new (
90
- HashlibInsecureHashFunction {
91
- string : hash_func_name. to_string ( ) ,
92
- } ,
93
- name_arg. range ( ) ,
94
- ) ) ;
95
- }
96
- }
97
- }
90
+ match weak_hash_call {
91
+ WeakHashCall :: Hashlib { call : hashlib_call } => {
92
+ detect_insecure_hashlib_calls ( checker, call, hashlib_call) ;
98
93
}
99
- HashlibCall :: WeakHash ( func_name) => {
94
+ WeakHashCall :: Crypt => detect_insecure_crypt_calls ( checker, call) ,
95
+ }
96
+ }
97
+ }
98
+
99
+ fn detect_insecure_hashlib_calls (
100
+ checker : & mut Checker ,
101
+ call : & ast:: ExprCall ,
102
+ hashlib_call : HashlibCall ,
103
+ ) {
104
+ if !is_used_for_security ( & call. arguments ) {
105
+ return ;
106
+ }
107
+
108
+ match hashlib_call {
109
+ HashlibCall :: New => {
110
+ let Some ( name_arg) = call. arguments . find_argument ( "name" , 0 ) else {
111
+ return ;
112
+ } ;
113
+ let Some ( hash_func_name) = string_literal ( name_arg) else {
114
+ return ;
115
+ } ;
116
+
117
+ // `hashlib.new` accepts both lowercase and uppercase names for hash
118
+ // functions.
119
+ if matches ! (
120
+ hash_func_name,
121
+ "md4" | "md5" | "sha" | "sha1" | "MD4" | "MD5" | "SHA" | "SHA1"
122
+ ) {
100
123
checker. diagnostics . push ( Diagnostic :: new (
101
124
HashlibInsecureHashFunction {
102
- string : ( * func_name) . to_string ( ) ,
125
+ library : "hashlib" . to_string ( ) ,
126
+ string : hash_func_name. to_string ( ) ,
103
127
} ,
104
- call . func . range ( ) ,
128
+ name_arg . range ( ) ,
105
129
) ) ;
106
130
}
107
131
}
132
+ HashlibCall :: WeakHash ( func_name) => {
133
+ checker. diagnostics . push ( Diagnostic :: new (
134
+ HashlibInsecureHashFunction {
135
+ library : "hashlib" . to_string ( ) ,
136
+ string : ( * func_name) . to_string ( ) ,
137
+ } ,
138
+ call. func . range ( ) ,
139
+ ) ) ;
140
+ }
141
+ }
142
+ }
143
+
144
+ fn detect_insecure_crypt_calls ( checker : & mut Checker , call : & ast:: ExprCall ) {
145
+ let Some ( method) = checker
146
+ . semantic ( )
147
+ . resolve_qualified_name ( & call. func )
148
+ . and_then ( |qualified_name| match qualified_name. segments ( ) {
149
+ [ "crypt" , "crypt" ] => Some ( ( "salt" , 1 ) ) ,
150
+ [ "crypt" , "mksalt" ] => Some ( ( "method" , 0 ) ) ,
151
+ _ => None ,
152
+ } )
153
+ . and_then ( |( argument_name, position) | {
154
+ call. arguments . find_argument ( argument_name, position)
155
+ } )
156
+ else {
157
+ return ;
158
+ } ;
159
+
160
+ let Some ( qualified_name) = checker. semantic ( ) . resolve_qualified_name ( method) else {
161
+ return ;
162
+ } ;
163
+
164
+ if matches ! (
165
+ qualified_name. segments( ) ,
166
+ [ "crypt" , "METHOD_CRYPT" | "METHOD_MD5" | "METHOD_BLOWFISH" ]
167
+ ) {
168
+ checker. diagnostics . push ( Diagnostic :: new (
169
+ HashlibInsecureHashFunction {
170
+ library : "crypt" . to_string ( ) ,
171
+ string : qualified_name. to_string ( ) ,
172
+ } ,
173
+ method. range ( ) ,
174
+ ) ) ;
108
175
}
109
176
}
110
177
@@ -114,7 +181,13 @@ fn is_used_for_security(arguments: &Arguments) -> bool {
114
181
. map_or ( true , |keyword| !is_const_false ( & keyword. value ) )
115
182
}
116
183
117
- #[ derive( Debug ) ]
184
+ #[ derive( Debug , Copy , Clone ) ]
185
+ enum WeakHashCall {
186
+ Hashlib { call : HashlibCall } ,
187
+ Crypt ,
188
+ }
189
+
190
+ #[ derive( Debug , Copy , Clone ) ]
118
191
enum HashlibCall {
119
192
New ,
120
193
WeakHash ( & ' static str ) ,
0 commit comments