mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 04:19:18 +00:00
[flake8-bandit
] Implement S502
SslInsecureVersion
rule (#9390)
## Summary Adds S502 rule for the [flake8-bandit](https://github.com/tylerwince/flake8-bandit) plugin port. Checks for calls to any function with keywords arguments `ssl_version` or `method` or for kwargs `method` in calls to `OpenSSL.SSL.Context` and `ssl_version` in calls to `ssl.wrap_socket` which have an insecure ssl_version valu. See also https://bandit.readthedocs.io/en/latest/_modules/bandit/plugins/insecure_ssl_tls.html#ssl_with_bad_version ## Test Plan Fixture added ## Issue Link Refers: https://github.com/astral-sh/ruff/issues/1646
This commit is contained in:
parent
60ba7a7c0d
commit
6dfc1ccd6f
8 changed files with 203 additions and 0 deletions
16
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S502.py
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S502.py
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
import ssl
|
||||
from ssl import wrap_socket
|
||||
from OpenSSL import SSL
|
||||
from OpenSSL.SSL import Context
|
||||
|
||||
wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3) # S502
|
||||
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1) # S502
|
||||
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2) # S502
|
||||
SSL.Context(method=SSL.SSLv2_METHOD) # S502
|
||||
SSL.Context(method=SSL.SSLv23_METHOD) # S502
|
||||
Context(method=SSL.SSLv3_METHOD) # S502
|
||||
Context(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
|
|
@ -968,6 +968,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||
if checker.enabled(Rule::SslWithNoVersion) {
|
||||
flake8_bandit::rules::ssl_with_no_version(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::SslInsecureVersion) {
|
||||
flake8_bandit::rules::ssl_insecure_version(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(dict) => {
|
||||
if checker.any_enabled(&[
|
||||
|
|
|
@ -642,6 +642,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Flake8Bandit, "413") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousPycryptoImport),
|
||||
(Flake8Bandit, "415") => (RuleGroup::Preview, rules::flake8_bandit::rules::SuspiciousPyghmiImport),
|
||||
(Flake8Bandit, "501") => (RuleGroup::Stable, rules::flake8_bandit::rules::RequestWithNoCertValidation),
|
||||
(Flake8Bandit, "502") => (RuleGroup::Preview, rules::flake8_bandit::rules::SslInsecureVersion),
|
||||
(Flake8Bandit, "504") => (RuleGroup::Preview, rules::flake8_bandit::rules::SslWithNoVersion),
|
||||
(Flake8Bandit, "505") => (RuleGroup::Preview, rules::flake8_bandit::rules::WeakCryptographicKey),
|
||||
(Flake8Bandit, "506") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnsafeYAMLLoad),
|
||||
|
|
|
@ -36,6 +36,7 @@ mod tests {
|
|||
#[test_case(Rule::SSHNoHostKeyVerification, Path::new("S507.py"))]
|
||||
#[test_case(Rule::SnmpInsecureVersion, Path::new("S508.py"))]
|
||||
#[test_case(Rule::SnmpWeakCryptography, Path::new("S509.py"))]
|
||||
#[test_case(Rule::SslInsecureVersion, Path::new("S502.py"))]
|
||||
#[test_case(Rule::SslWithNoVersion, Path::new("S504.py"))]
|
||||
#[test_case(Rule::StartProcessWithAShell, Path::new("S605.py"))]
|
||||
#[test_case(Rule::StartProcessWithNoShell, Path::new("S606.py"))]
|
||||
|
|
|
@ -20,6 +20,7 @@ pub(crate) use shell_injection::*;
|
|||
pub(crate) use snmp_insecure_version::*;
|
||||
pub(crate) use snmp_weak_cryptography::*;
|
||||
pub(crate) use ssh_no_host_key_verification::*;
|
||||
pub(crate) use ssl_insecure_version::*;
|
||||
pub(crate) use ssl_with_no_version::*;
|
||||
pub(crate) use suspicious_function_call::*;
|
||||
pub(crate) use suspicious_imports::*;
|
||||
|
@ -51,6 +52,7 @@ mod shell_injection;
|
|||
mod snmp_insecure_version;
|
||||
mod snmp_weak_cryptography;
|
||||
mod ssh_no_host_key_verification;
|
||||
mod ssl_insecure_version;
|
||||
mod ssl_with_no_version;
|
||||
mod suspicious_function_call;
|
||||
mod suspicious_imports;
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// 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. 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
|
||||
/// import ssl
|
||||
///
|
||||
/// ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import ssl
|
||||
///
|
||||
/// ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_2)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct SslInsecureVersion {
|
||||
protocol: String,
|
||||
}
|
||||
|
||||
impl Violation for SslInsecureVersion {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let SslInsecureVersion { protocol } = self;
|
||||
format!("Call made with insecure SSL protocol: `{protocol}`")
|
||||
}
|
||||
}
|
||||
|
||||
/// S502
|
||||
pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) {
|
||||
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;
|
||||
};
|
||||
|
||||
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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
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"
|
||||
)
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S502.py:6:13: S502 Call made with insecure SSL protocol: `PROTOCOL_SSLv3`
|
||||
|
|
||||
4 | from OpenSSL.SSL import Context
|
||||
5 |
|
||||
6 | wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3) # S502
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S502
|
||||
7 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1) # S502
|
||||
8 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2) # S502
|
||||
|
|
||||
|
||||
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
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S502
|
||||
8 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2) # S502
|
||||
9 | SSL.Context(method=SSL.SSLv2_METHOD) # S502
|
||||
|
|
||||
|
||||
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
|
||||
8 | ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2) # S502
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S502
|
||||
9 | SSL.Context(method=SSL.SSLv2_METHOD) # S502
|
||||
10 | SSL.Context(method=SSL.SSLv23_METHOD) # S502
|
||||
|
|
||||
|
||||
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
|
||||
9 | SSL.Context(method=SSL.SSLv2_METHOD) # S502
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ S502
|
||||
10 | SSL.Context(method=SSL.SSLv23_METHOD) # S502
|
||||
11 | Context(method=SSL.SSLv3_METHOD) # S502
|
||||
|
|
||||
|
||||
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
|
||||
10 | SSL.Context(method=SSL.SSLv23_METHOD) # S502
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ S502
|
||||
11 | Context(method=SSL.SSLv3_METHOD) # S502
|
||||
12 | Context(method=SSL.TLSv1_METHOD) # S502
|
||||
|
|
||||
|
||||
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
|
||||
11 | Context(method=SSL.SSLv3_METHOD) # S502
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ S502
|
||||
12 | Context(method=SSL.TLSv1_METHOD) # S502
|
||||
|
|
||||
|
||||
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
|
||||
13 |
|
||||
14 | wrap_socket(ssl_version=ssl.PROTOCOL_TLS_CLIENT) # OK
|
||||
|
|
||||
|
||||
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -3512,6 +3512,7 @@
|
|||
"S5",
|
||||
"S50",
|
||||
"S501",
|
||||
"S502",
|
||||
"S504",
|
||||
"S505",
|
||||
"S506",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue