diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S502.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S502.py index 1d0751b1f35e84..5d8118b15874e9 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S502.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S502.py @@ -11,23 +11,6 @@ Context(method=SSL.SSLv3_METHOD) # S502 Context(method=SSL.TLSv1_METHOD) # S502 - -def func(): - pass - - -func(ssl_version=ssl.PROTOCOL_SSLv2) # S502 -func(ssl_version=ssl.PROTOCOL_SSLv3) # S502 -func(ssl_version=ssl.PROTOCOL_TLSv1) # S502 -func(method=SSL.SSLv2_METHOD) # S502 -func(method=SSL.SSLv23_METHOD) # S502 -func(method=SSL.SSLv3_METHOD) # S502 -func(method=SSL.TLSv1_METHOD) # S502 - wrap_socket(ssl_version=ssl.PROTOCOL_TLS_CLIENT) # OK SSL.Context(method=SSL.TLS_SERVER_METHOD) # OK func(ssl_version=ssl.PROTOCOL_TLSv1_2) # OK - - - - diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs index 9f300c8524cee9..e3145c43068bd7 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs @@ -1,22 +1,25 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, ExprCall}; -use ruff_python_semantic::analyze::typing::find_assigned_value; +use ruff_text_size::Ranged; use crate::checkers::ast::Checker; /// ## What it does -/// Checks for calls to Python methods with parameters that indicate the used broken SSL/TLS -/// protocol versions. +/// Checks for function calls with parameters that indicate the use of insecure +/// SSL and TLS protocol versions. /// /// ## Why is this bad? -/// Several highly publicized exploitable flaws have been discovered in all versions of SSL and -/// early versions of TLS. It is strongly recommended that use of the following known broken -/// protocol versions be avoided: -/// - SSL v2 -/// - SSL v3 -/// - TLS v1 -/// - TLS v1.1 +/// Several highly publicized exploitable flaws have been discovered in all +/// versions of SSL and early versions of TLS. The following versions are +/// considered insecure, and should be avoided: +/// - SSL v2 +/// - SSL v3 +/// - TLS v1 +/// - TLS v1.1 +/// +/// This method supports detection on the Python's built-in `ssl` module and +/// the `pyOpenSSL` module. /// /// ## Example /// ```python @@ -39,81 +42,66 @@ pub struct SslInsecureVersion { impl Violation for SslInsecureVersion { #[derive_message_formats] fn message(&self) -> String { - format!("Call made with insecure SSL protocol: {}", self.protocol) + let SslInsecureVersion { protocol } = self; + format!("Call made with insecure SSL protocol: `{protocol}`") } } -const INSECURE_SSL_PROTOCOLS: &[&str] = &[ - "PROTOCOL_SSLv2", - "PROTOCOL_SSLv3", - "PROTOCOL_TLSv1", - "PROTOCOL_TLSv1_1", - "SSLv2_METHOD", - "SSLv23_METHOD", - "SSLv3_METHOD", - "TLSv1_METHOD", - "TLSv1_1_METHOD", -]; - /// S502 pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) { - let keywords = match checker.semantic().resolve_call_path(call.func.as_ref()) { - Some(call_path) => match *call_path.as_slice() { - ["ssl", "wrap_socket"] => vec!["ssl_version"], - ["OpenSSL", "SSL", "Context"] => vec!["method"], - _ => vec!["ssl_version", "method"], - }, - None => vec!["ssl_version", "method"], + let Some(keyword) = checker + .semantic() + .resolve_call_path(call.func.as_ref()) + .and_then(|call_path| match call_path.as_slice() { + ["ssl", "wrap_socket"] => Some("ssl_version"), + ["OpenSSL", "SSL", "Context"] => Some("method"), + _ => None, + }) + else { + return; }; - for arg in keywords { - let Some(keyword) = call.arguments.find_keyword(arg) else { - continue; - }; - match &keyword.value { - Expr::Name(ast::ExprName { id, .. }) => { - let Some(val) = find_assigned_value(id, checker.semantic()) else { - continue; - }; - match val { - Expr::Name(ast::ExprName { id, .. }) => { - if INSECURE_SSL_PROTOCOLS.contains(&id.as_str()) { - checker.diagnostics.push(Diagnostic::new( - SslInsecureVersion { - protocol: id.to_string(), - }, - keyword.range, - )); - } - } - Expr::Attribute(ast::ExprAttribute { attr, .. }) => { - if INSECURE_SSL_PROTOCOLS.contains(&attr.as_str()) { - checker.diagnostics.push(Diagnostic::new( - SslInsecureVersion { - protocol: attr.to_string(), - }, - keyword.range, - )); - } - } - _ => { - continue; - } - } - } - Expr::Attribute(ast::ExprAttribute { attr, .. }) => { - if INSECURE_SSL_PROTOCOLS.contains(&attr.as_str()) { - checker.diagnostics.push(Diagnostic::new( - SslInsecureVersion { - protocol: attr.to_string(), - }, - keyword.range, - )); - } + let Some(keyword) = call.arguments.find_keyword(keyword) else { + return; + }; + + match &keyword.value { + Expr::Name(ast::ExprName { id, .. }) => { + if is_insecure_protocol(&id) { + checker.diagnostics.push(Diagnostic::new( + SslInsecureVersion { + protocol: id.to_string(), + }, + keyword.range(), + )); } - _ => { - continue; + } + Expr::Attribute(ast::ExprAttribute { attr, .. }) => { + if is_insecure_protocol(&attr) { + checker.diagnostics.push(Diagnostic::new( + SslInsecureVersion { + protocol: attr.to_string(), + }, + keyword.range(), + )); } } + _ => {} } } + +/// Returns `true` if the given protocol name is insecure. +fn is_insecure_protocol(name: &str) -> bool { + matches!( + name, + "PROTOCOL_SSLv2" + | "PROTOCOL_SSLv3" + | "PROTOCOL_TLSv1" + | "PROTOCOL_TLSv1_1" + | "SSLv2_METHOD" + | "SSLv23_METHOD" + | "SSLv3_METHOD" + | "TLSv1_METHOD" + | "TLSv1_1_METHOD" + ) +} diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S502_S502.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S502_S502.py.snap index d3aeb02c12d105..31a2a690416c5d 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S502_S502.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S502_S502.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs --- -S502.py:6:13: S502 Call made with insecure SSL protocol: PROTOCOL_SSLv3 +S502.py:6:13: S502 Call made with insecure SSL protocol: `PROTOCOL_SSLv3` | 4 | from OpenSSL.SSL import Context 5 | @@ -11,7 +11,7 @@ S502.py:6:13: S502 Call made with insecure SSL protocol: PROTOCOL_SSLv3 8 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2) # S502 | -S502.py:7:17: S502 Call made with insecure SSL protocol: PROTOCOL_TLSv1 +S502.py:7:17: S502 Call made with insecure SSL protocol: `PROTOCOL_TLSv1` | 6 | wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3) # S502 7 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1) # S502 @@ -20,7 +20,7 @@ S502.py:7:17: S502 Call made with insecure SSL protocol: PROTOCOL_TLSv1 9 | SSL.Context(method=SSL.SSLv2_METHOD) # S502 | -S502.py:8:17: S502 Call made with insecure SSL protocol: PROTOCOL_SSLv2 +S502.py:8:17: S502 Call made with insecure SSL protocol: `PROTOCOL_SSLv2` | 6 | wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3) # S502 7 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1) # S502 @@ -30,7 +30,7 @@ S502.py:8:17: S502 Call made with insecure SSL protocol: PROTOCOL_SSLv2 10 | SSL.Context(method=SSL.SSLv23_METHOD) # S502 | -S502.py:9:13: S502 Call made with insecure SSL protocol: SSLv2_METHOD +S502.py:9:13: S502 Call made with insecure SSL protocol: `SSLv2_METHOD` | 7 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1) # S502 8 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2) # S502 @@ -40,7 +40,7 @@ S502.py:9:13: S502 Call made with insecure SSL protocol: SSLv2_METHOD 11 | Context(method=SSL.SSLv3_METHOD) # S502 | -S502.py:10:13: S502 Call made with insecure SSL protocol: SSLv23_METHOD +S502.py:10:13: S502 Call made with insecure SSL protocol: `SSLv23_METHOD` | 8 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2) # S502 9 | SSL.Context(method=SSL.SSLv2_METHOD) # S502 @@ -50,7 +50,7 @@ S502.py:10:13: S502 Call made with insecure SSL protocol: SSLv23_METHOD 12 | Context(method=SSL.TLSv1_METHOD) # S502 | -S502.py:11:9: S502 Call made with insecure SSL protocol: SSLv3_METHOD +S502.py:11:9: S502 Call made with insecure SSL protocol: `SSLv3_METHOD` | 9 | SSL.Context(method=SSL.SSLv2_METHOD) # S502 10 | SSL.Context(method=SSL.SSLv23_METHOD) # S502 @@ -59,78 +59,14 @@ S502.py:11:9: S502 Call made with insecure SSL protocol: SSLv3_METHOD 12 | Context(method=SSL.TLSv1_METHOD) # S502 | -S502.py:12:9: S502 Call made with insecure SSL protocol: TLSv1_METHOD +S502.py:12:9: S502 Call made with insecure SSL protocol: `TLSv1_METHOD` | 10 | SSL.Context(method=SSL.SSLv23_METHOD) # S502 11 | Context(method=SSL.SSLv3_METHOD) # S502 12 | Context(method=SSL.TLSv1_METHOD) # S502 | ^^^^^^^^^^^^^^^^^^^^^^^ S502 - | - -S502.py:19:6: S502 Call made with insecure SSL protocol: PROTOCOL_SSLv2 - | -19 | func(ssl_version=ssl.PROTOCOL_SSLv2) # S502 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S502 -20 | func(ssl_version=ssl.PROTOCOL_SSLv3) # S502 -21 | func(ssl_version=ssl.PROTOCOL_TLSv1) # S502 - | - -S502.py:20:6: S502 Call made with insecure SSL protocol: PROTOCOL_SSLv3 - | -19 | func(ssl_version=ssl.PROTOCOL_SSLv2) # S502 -20 | func(ssl_version=ssl.PROTOCOL_SSLv3) # S502 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S502 -21 | func(ssl_version=ssl.PROTOCOL_TLSv1) # S502 -22 | func(method=SSL.SSLv2_METHOD) # S502 - | - -S502.py:21:6: S502 Call made with insecure SSL protocol: PROTOCOL_TLSv1 - | -19 | func(ssl_version=ssl.PROTOCOL_SSLv2) # S502 -20 | func(ssl_version=ssl.PROTOCOL_SSLv3) # S502 -21 | func(ssl_version=ssl.PROTOCOL_TLSv1) # S502 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S502 -22 | func(method=SSL.SSLv2_METHOD) # S502 -23 | func(method=SSL.SSLv23_METHOD) # S502 - | - -S502.py:22:6: S502 Call made with insecure SSL protocol: SSLv2_METHOD - | -20 | func(ssl_version=ssl.PROTOCOL_SSLv3) # S502 -21 | func(ssl_version=ssl.PROTOCOL_TLSv1) # S502 -22 | func(method=SSL.SSLv2_METHOD) # S502 - | ^^^^^^^^^^^^^^^^^^^^^^^ S502 -23 | func(method=SSL.SSLv23_METHOD) # S502 -24 | func(method=SSL.SSLv3_METHOD) # S502 - | - -S502.py:23:6: S502 Call made with insecure SSL protocol: SSLv23_METHOD - | -21 | func(ssl_version=ssl.PROTOCOL_TLSv1) # S502 -22 | func(method=SSL.SSLv2_METHOD) # S502 -23 | func(method=SSL.SSLv23_METHOD) # S502 - | ^^^^^^^^^^^^^^^^^^^^^^^^ S502 -24 | func(method=SSL.SSLv3_METHOD) # S502 -25 | func(method=SSL.TLSv1_METHOD) # S502 - | - -S502.py:24:6: S502 Call made with insecure SSL protocol: SSLv3_METHOD - | -22 | func(method=SSL.SSLv2_METHOD) # S502 -23 | func(method=SSL.SSLv23_METHOD) # S502 -24 | func(method=SSL.SSLv3_METHOD) # S502 - | ^^^^^^^^^^^^^^^^^^^^^^^ S502 -25 | func(method=SSL.TLSv1_METHOD) # S502 - | - -S502.py:25:6: S502 Call made with insecure SSL protocol: TLSv1_METHOD - | -23 | func(method=SSL.SSLv23_METHOD) # S502 -24 | func(method=SSL.SSLv3_METHOD) # S502 -25 | func(method=SSL.TLSv1_METHOD) # S502 - | ^^^^^^^^^^^^^^^^^^^^^^^ S502 -26 | -27 | wrap_socket(ssl_version=ssl.PROTOCOL_TLS_CLIENT) # OK +13 | +14 | wrap_socket(ssl_version=ssl.PROTOCOL_TLS_CLIENT) # OK |