diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S324.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S324.py index bea55067ea77e2..2b72ed2622da25 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S324.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S324.py @@ -1,52 +1,49 @@ +import crypt import hashlib from hashlib import new as hashlib_new from hashlib import sha1 as hashlib_sha1 # Invalid - hashlib.new('md5') - hashlib.new('md4', b'test') - hashlib.new(name='md5', data=b'test') - hashlib.new('MD4', data=b'test') - hashlib.new('sha1') - hashlib.new('sha1', data=b'test') - hashlib.new('sha', data=b'test') - hashlib.new(name='SHA', data=b'test') - hashlib.sha(data=b'test') - hashlib.md5() - hashlib_new('sha1') - hashlib_sha1('sha1') # usedforsecurity arg only available in Python 3.9+ hashlib.new('sha1', usedforsecurity=True) -# Valid +crypt.crypt("test", salt=crypt.METHOD_CRYPT) +crypt.crypt("test", salt=crypt.METHOD_MD5) +crypt.crypt("test", salt=crypt.METHOD_BLOWFISH) +crypt.crypt("test", crypt.METHOD_BLOWFISH) -hashlib.new('sha256') +crypt.mksalt(crypt.METHOD_CRYPT) +crypt.mksalt(crypt.METHOD_MD5) +crypt.mksalt(crypt.METHOD_BLOWFISH) +# Valid +hashlib.new('sha256') hashlib.new('SHA512') - hashlib.sha256(data=b'test') # usedforsecurity arg only available in Python 3.9+ hashlib_new(name='sha1', usedforsecurity=False) - -# usedforsecurity arg only available in Python 3.9+ hashlib_sha1(name='sha1', usedforsecurity=False) - -# usedforsecurity arg only available in Python 3.9+ hashlib.md4(usedforsecurity=False) - -# usedforsecurity arg only available in Python 3.9+ hashlib.new(name='sha256', usedforsecurity=False) + +crypt.crypt("test") +crypt.crypt("test", salt=crypt.METHOD_SHA256) +crypt.crypt("test", salt=crypt.METHOD_SHA512) + +crypt.mksalt() +crypt.mksalt(crypt.METHOD_SHA256) +crypt.mksalt(crypt.METHOD_SHA512) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs index 38520e804bd745..a2306b70a692ec 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs @@ -9,7 +9,8 @@ use crate::checkers::ast::Checker; use super::super::helpers::string_literal; /// ## What it does -/// Checks for uses of weak or broken cryptographic hash functions. +/// Checks for uses of weak or broken cryptographic hash functions in +/// `hashlib` and `crypt` libraries. /// /// ## Why is this bad? /// Weak or broken cryptographic hash functions may be susceptible to @@ -43,66 +44,123 @@ use super::super::helpers::string_literal; /// /// ## References /// - [Python documentation: `hashlib` — Secure hashes and message digests](https://docs.python.org/3/library/hashlib.html) +/// - [Python documentation: `crypt` — Function to check Unix passwords](https://docs.python.org/3/library/crypt.html) /// - [Common Weakness Enumeration: CWE-327](https://cwe.mitre.org/data/definitions/327.html) /// - [Common Weakness Enumeration: CWE-328](https://cwe.mitre.org/data/definitions/328.html) /// - [Common Weakness Enumeration: CWE-916](https://cwe.mitre.org/data/definitions/916.html) #[violation] pub struct HashlibInsecureHashFunction { + library: String, string: String, } impl Violation for HashlibInsecureHashFunction { #[derive_message_formats] fn message(&self) -> String { - let HashlibInsecureHashFunction { string } = self; - format!("Probable use of insecure hash functions in `hashlib`: `{string}`") + let HashlibInsecureHashFunction { library, string } = self; + format!("Probable use of insecure hash functions in `{library}`: `{string}`") } } /// S324 pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast::ExprCall) { - if let Some(hashlib_call) = checker + if let Some(weak_hash_call) = checker .semantic() .resolve_qualified_name(&call.func) .and_then(|qualified_name| match qualified_name.segments() { - ["hashlib", "new"] => Some(HashlibCall::New), - ["hashlib", "md4"] => Some(HashlibCall::WeakHash("md4")), - ["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")), - ["hashlib", "sha"] => Some(HashlibCall::WeakHash("sha")), - ["hashlib", "sha1"] => Some(HashlibCall::WeakHash("sha1")), + ["hashlib", "new"] => Some(WeakHashCall::Hashlib { + method: HashlibMethod::New, + }), + ["hashlib", "md4"] => Some(WeakHashCall::Hashlib { + method: HashlibMethod::WeakHash("md4"), + }), + ["hashlib", "md5"] => Some(WeakHashCall::Hashlib { + method: HashlibMethod::WeakHash("md5"), + }), + ["hashlib", "sha"] => Some(WeakHashCall::Hashlib { + method: HashlibMethod::WeakHash("sha"), + }), + ["hashlib", "sha1"] => Some(WeakHashCall::Hashlib { + method: HashlibMethod::WeakHash("sha1"), + }), + ["crypt", "crypt" | "mksalt"] => Some(WeakHashCall::Crypt), _ => None, }) { - if !is_used_for_security(&call.arguments) { - return; + match weak_hash_call { + WeakHashCall::Hashlib { method } => { + detect_insecure_hashlib_calls(checker, call, &method); + } + WeakHashCall::Crypt => detect_insecure_crypt_calls(checker, call), } - match hashlib_call { - HashlibCall::New => { - if let Some(name_arg) = call.arguments.find_argument("name", 0) { - if let Some(hash_func_name) = string_literal(name_arg) { - // `hashlib.new` accepts both lowercase and uppercase names for hash - // functions. - if matches!( - hash_func_name, - "md4" | "md5" | "sha" | "sha1" | "MD4" | "MD5" | "SHA" | "SHA1" - ) { - checker.diagnostics.push(Diagnostic::new( - HashlibInsecureHashFunction { - string: hash_func_name.to_string(), - }, - name_arg.range(), - )); - } + } +} + +fn detect_insecure_hashlib_calls( + checker: &mut Checker, + call: &ast::ExprCall, + method: &HashlibMethod, +) { + if !is_used_for_security(&call.arguments) { + return; + } + + match method { + HashlibMethod::New => { + if let Some(name_arg) = call.arguments.find_argument("name", 0) { + if let Some(hash_func_name) = string_literal(name_arg) { + // `hashlib.new` accepts both lowercase and uppercase names for hash + // functions. + if matches!( + hash_func_name, + "md4" | "md5" | "sha" | "sha1" | "MD4" | "MD5" | "SHA" | "SHA1" + ) { + checker.diagnostics.push(Diagnostic::new( + HashlibInsecureHashFunction { + library: "hashlib".to_string(), + string: hash_func_name.to_string(), + }, + name_arg.range(), + )); } } } - HashlibCall::WeakHash(func_name) => { - checker.diagnostics.push(Diagnostic::new( - HashlibInsecureHashFunction { - string: (*func_name).to_string(), - }, - call.func.range(), - )); + } + HashlibMethod::WeakHash(func_name) => { + checker.diagnostics.push(Diagnostic::new( + HashlibInsecureHashFunction { + library: "hashlib".to_string(), + string: (*func_name).to_string(), + }, + call.func.range(), + )); + } + } +} + +fn detect_insecure_crypt_calls(checker: &mut Checker, call: &ast::ExprCall) { + if let Some((argument_name, position)) = checker + .semantic() + .resolve_qualified_name(&call.func) + .and_then(|qualified_name| match qualified_name.segments() { + ["crypt", "crypt"] => Some(("salt", 1)), + ["crypt", "mksalt"] => Some(("method", 0)), + _ => None, + }) + { + if let Some(method) = call.arguments.find_argument(argument_name, position) { + if let Some(qualified_name) = checker.semantic().resolve_qualified_name(method) { + if let ["crypt", "METHOD_CRYPT" | "METHOD_MD5" | "METHOD_BLOWFISH"] = + qualified_name.segments() + { + checker.diagnostics.push(Diagnostic::new( + HashlibInsecureHashFunction { + library: "crypt".to_string(), + string: qualified_name.to_string(), + }, + method.range(), + )); + } } } } @@ -115,7 +173,13 @@ fn is_used_for_security(arguments: &Arguments) -> bool { } #[derive(Debug)] -enum HashlibCall { +enum WeakHashCall { + Hashlib { method: HashlibMethod }, + Crypt, +} + +#[derive(Debug)] +enum HashlibMethod { New, WeakHash(&'static str), } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S324_S324.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S324_S324.py.snap index 8cd080b375c846..81a29e01bd5c5b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S324_S324.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S324_S324.py.snap @@ -3,131 +3,193 @@ source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs --- S324.py:7:13: S324 Probable use of insecure hash functions in `hashlib`: `md5` | -5 | # Invalid -6 | +6 | # Invalid 7 | hashlib.new('md5') | ^^^^^ S324 -8 | -9 | hashlib.new('md4', b'test') +8 | hashlib.new('md4', b'test') +9 | hashlib.new(name='md5', data=b'test') | -S324.py:9:13: S324 Probable use of insecure hash functions in `hashlib`: `md4` +S324.py:8:13: S324 Probable use of insecure hash functions in `hashlib`: `md4` | + 6 | # Invalid 7 | hashlib.new('md5') - 8 | - 9 | hashlib.new('md4', b'test') + 8 | hashlib.new('md4', b'test') | ^^^^^ S324 -10 | -11 | hashlib.new(name='md5', data=b'test') + 9 | hashlib.new(name='md5', data=b'test') +10 | hashlib.new('MD4', data=b'test') | -S324.py:11:18: S324 Probable use of insecure hash functions in `hashlib`: `md5` +S324.py:9:18: S324 Probable use of insecure hash functions in `hashlib`: `md5` | - 9 | hashlib.new('md4', b'test') -10 | -11 | hashlib.new(name='md5', data=b'test') + 7 | hashlib.new('md5') + 8 | hashlib.new('md4', b'test') + 9 | hashlib.new(name='md5', data=b'test') | ^^^^^ S324 -12 | -13 | hashlib.new('MD4', data=b'test') +10 | hashlib.new('MD4', data=b'test') +11 | hashlib.new('sha1') | -S324.py:13:13: S324 Probable use of insecure hash functions in `hashlib`: `MD4` +S324.py:10:13: S324 Probable use of insecure hash functions in `hashlib`: `MD4` | -11 | hashlib.new(name='md5', data=b'test') -12 | -13 | hashlib.new('MD4', data=b'test') + 8 | hashlib.new('md4', b'test') + 9 | hashlib.new(name='md5', data=b'test') +10 | hashlib.new('MD4', data=b'test') | ^^^^^ S324 -14 | -15 | hashlib.new('sha1') +11 | hashlib.new('sha1') +12 | hashlib.new('sha1', data=b'test') | -S324.py:15:13: S324 Probable use of insecure hash functions in `hashlib`: `sha1` +S324.py:11:13: S324 Probable use of insecure hash functions in `hashlib`: `sha1` | -13 | hashlib.new('MD4', data=b'test') -14 | -15 | hashlib.new('sha1') + 9 | hashlib.new(name='md5', data=b'test') +10 | hashlib.new('MD4', data=b'test') +11 | hashlib.new('sha1') | ^^^^^^ S324 -16 | -17 | hashlib.new('sha1', data=b'test') +12 | hashlib.new('sha1', data=b'test') +13 | hashlib.new('sha', data=b'test') | -S324.py:17:13: S324 Probable use of insecure hash functions in `hashlib`: `sha1` +S324.py:12:13: S324 Probable use of insecure hash functions in `hashlib`: `sha1` | -15 | hashlib.new('sha1') -16 | -17 | hashlib.new('sha1', data=b'test') +10 | hashlib.new('MD4', data=b'test') +11 | hashlib.new('sha1') +12 | hashlib.new('sha1', data=b'test') | ^^^^^^ S324 -18 | -19 | hashlib.new('sha', data=b'test') +13 | hashlib.new('sha', data=b'test') +14 | hashlib.new(name='SHA', data=b'test') | -S324.py:19:13: S324 Probable use of insecure hash functions in `hashlib`: `sha` +S324.py:13:13: S324 Probable use of insecure hash functions in `hashlib`: `sha` | -17 | hashlib.new('sha1', data=b'test') -18 | -19 | hashlib.new('sha', data=b'test') +11 | hashlib.new('sha1') +12 | hashlib.new('sha1', data=b'test') +13 | hashlib.new('sha', data=b'test') | ^^^^^ S324 -20 | -21 | hashlib.new(name='SHA', data=b'test') +14 | hashlib.new(name='SHA', data=b'test') +15 | hashlib.sha(data=b'test') | -S324.py:21:18: S324 Probable use of insecure hash functions in `hashlib`: `SHA` +S324.py:14:18: S324 Probable use of insecure hash functions in `hashlib`: `SHA` | -19 | hashlib.new('sha', data=b'test') -20 | -21 | hashlib.new(name='SHA', data=b'test') +12 | hashlib.new('sha1', data=b'test') +13 | hashlib.new('sha', data=b'test') +14 | hashlib.new(name='SHA', data=b'test') | ^^^^^ S324 -22 | -23 | hashlib.sha(data=b'test') +15 | hashlib.sha(data=b'test') +16 | hashlib.md5() | -S324.py:23:1: S324 Probable use of insecure hash functions in `hashlib`: `sha` +S324.py:15:1: S324 Probable use of insecure hash functions in `hashlib`: `sha` | -21 | hashlib.new(name='SHA', data=b'test') -22 | -23 | hashlib.sha(data=b'test') +13 | hashlib.new('sha', data=b'test') +14 | hashlib.new(name='SHA', data=b'test') +15 | hashlib.sha(data=b'test') | ^^^^^^^^^^^ S324 -24 | -25 | hashlib.md5() +16 | hashlib.md5() +17 | hashlib_new('sha1') | -S324.py:25:1: S324 Probable use of insecure hash functions in `hashlib`: `md5` +S324.py:16:1: S324 Probable use of insecure hash functions in `hashlib`: `md5` | -23 | hashlib.sha(data=b'test') -24 | -25 | hashlib.md5() +14 | hashlib.new(name='SHA', data=b'test') +15 | hashlib.sha(data=b'test') +16 | hashlib.md5() | ^^^^^^^^^^^ S324 -26 | -27 | hashlib_new('sha1') +17 | hashlib_new('sha1') +18 | hashlib_sha1('sha1') | -S324.py:27:13: S324 Probable use of insecure hash functions in `hashlib`: `sha1` +S324.py:17:13: S324 Probable use of insecure hash functions in `hashlib`: `sha1` | -25 | hashlib.md5() -26 | -27 | hashlib_new('sha1') +15 | hashlib.sha(data=b'test') +16 | hashlib.md5() +17 | hashlib_new('sha1') | ^^^^^^ S324 -28 | -29 | hashlib_sha1('sha1') +18 | hashlib_sha1('sha1') | -S324.py:29:1: S324 Probable use of insecure hash functions in `hashlib`: `sha1` +S324.py:18:1: S324 Probable use of insecure hash functions in `hashlib`: `sha1` | -27 | hashlib_new('sha1') -28 | -29 | hashlib_sha1('sha1') +16 | hashlib.md5() +17 | hashlib_new('sha1') +18 | hashlib_sha1('sha1') | ^^^^^^^^^^^^ S324 -30 | -31 | # usedforsecurity arg only available in Python 3.9+ +19 | +20 | # usedforsecurity arg only available in Python 3.9+ | -S324.py:32:13: S324 Probable use of insecure hash functions in `hashlib`: `sha1` +S324.py:21:13: S324 Probable use of insecure hash functions in `hashlib`: `sha1` | -31 | # usedforsecurity arg only available in Python 3.9+ -32 | hashlib.new('sha1', usedforsecurity=True) +20 | # usedforsecurity arg only available in Python 3.9+ +21 | hashlib.new('sha1', usedforsecurity=True) | ^^^^^^ S324 -33 | -34 | # Valid +22 | +23 | crypt.crypt("test", salt=crypt.METHOD_CRYPT) + | + +S324.py:23:26: S324 Probable use of insecure hash functions in `crypt`: `crypt.METHOD_CRYPT` + | +21 | hashlib.new('sha1', usedforsecurity=True) +22 | +23 | crypt.crypt("test", salt=crypt.METHOD_CRYPT) + | ^^^^^^^^^^^^^^^^^^ S324 +24 | crypt.crypt("test", salt=crypt.METHOD_MD5) +25 | crypt.crypt("test", salt=crypt.METHOD_BLOWFISH) + | + +S324.py:24:26: S324 Probable use of insecure hash functions in `crypt`: `crypt.METHOD_MD5` + | +23 | crypt.crypt("test", salt=crypt.METHOD_CRYPT) +24 | crypt.crypt("test", salt=crypt.METHOD_MD5) + | ^^^^^^^^^^^^^^^^ S324 +25 | crypt.crypt("test", salt=crypt.METHOD_BLOWFISH) +26 | crypt.crypt("test", crypt.METHOD_BLOWFISH) | +S324.py:25:26: S324 Probable use of insecure hash functions in `crypt`: `crypt.METHOD_BLOWFISH` + | +23 | crypt.crypt("test", salt=crypt.METHOD_CRYPT) +24 | crypt.crypt("test", salt=crypt.METHOD_MD5) +25 | crypt.crypt("test", salt=crypt.METHOD_BLOWFISH) + | ^^^^^^^^^^^^^^^^^^^^^ S324 +26 | crypt.crypt("test", crypt.METHOD_BLOWFISH) + | +S324.py:26:21: S324 Probable use of insecure hash functions in `crypt`: `crypt.METHOD_BLOWFISH` + | +24 | crypt.crypt("test", salt=crypt.METHOD_MD5) +25 | crypt.crypt("test", salt=crypt.METHOD_BLOWFISH) +26 | crypt.crypt("test", crypt.METHOD_BLOWFISH) + | ^^^^^^^^^^^^^^^^^^^^^ S324 +27 | +28 | crypt.mksalt(crypt.METHOD_CRYPT) + | + +S324.py:28:14: S324 Probable use of insecure hash functions in `crypt`: `crypt.METHOD_CRYPT` + | +26 | crypt.crypt("test", crypt.METHOD_BLOWFISH) +27 | +28 | crypt.mksalt(crypt.METHOD_CRYPT) + | ^^^^^^^^^^^^^^^^^^ S324 +29 | crypt.mksalt(crypt.METHOD_MD5) +30 | crypt.mksalt(crypt.METHOD_BLOWFISH) + | + +S324.py:29:14: S324 Probable use of insecure hash functions in `crypt`: `crypt.METHOD_MD5` + | +28 | crypt.mksalt(crypt.METHOD_CRYPT) +29 | crypt.mksalt(crypt.METHOD_MD5) + | ^^^^^^^^^^^^^^^^ S324 +30 | crypt.mksalt(crypt.METHOD_BLOWFISH) + | + +S324.py:30:14: S324 Probable use of insecure hash functions in `crypt`: `crypt.METHOD_BLOWFISH` + | +28 | crypt.mksalt(crypt.METHOD_CRYPT) +29 | crypt.mksalt(crypt.METHOD_MD5) +30 | crypt.mksalt(crypt.METHOD_BLOWFISH) + | ^^^^^^^^^^^^^^^^^^^^^ S324 +31 | +32 | # Valid + |