CA2100 : Vérifier si les requêtes SQL présentent des failles de sécurité
Propriété | Value |
---|---|
Identificateur de la règle | CA2100 |
Titre | Vérifier si les requêtes SQL présentent des failles de sécurité |
Catégorie | Sécurité |
Le correctif est cassant ou non cassant | Sans rupture |
Activé par défaut dans .NET 8 | Non |
Cause
Une méthode définit la propriété System.Data.IDbCommand.CommandText à l’aide d’une chaîne générée à partir d’un argument de chaîne à la méthode.
Par défaut, cette règle analyse l’intégralité du codebase, mais elle est configurable.
Description de la règle
Cette règle suppose que toute chaîne, dont la valeur ne peut pas être déterminée au moment de la compilation, peut contenir une entrée utilisateur. Une chaîne de commande SQL générée par une entrée d'utilisateur est vulnérable aux attaques par injection de code SQL. Dans une attaque par injection de code SQL, un utilisateur malveillant fournit une entrée qui modifie la conception d’une requête dans le but d’endommager ou d’obtenir un accès non autorisé à la base de données sous-jacente. Les techniques courantes incluent l’injection d’un guillemet unique ou d’une apostrophe, qui est le délimiteur de chaîne littérale SQL ; deux tirets, ce qui signifie un commentaire SQL ; et un point-virgule, qui indique qu’une nouvelle commande suit. Si l’entrée utilisateur doit faire partie de la requête, utilisez l’une des options suivantes, répertoriées par ordre d’efficacité, pour réduire le risque d’attaque.
Utilisez une procédure stockée.
Utilisez une chaîne de commande paramétrable.
Validez l’entrée utilisateur pour le type et le contenu avant de générer la chaîne de commande.
Les types .NET suivants implémentent la propriété CommandText ou fournissent des constructeurs qui définissent la propriété à l’aide d’un argument de chaîne.
System.Data.Odbc.OdbcCommand et System.Data.Odbc.OdbcDataAdapter
System.Data.OleDb.OleDbCommand et System.Data.OleDb.OleDbDataAdapter
System.Data.OracleClient.OracleCommand et System.Data.OracleClient.OracleDataAdapter
System.Data.SqlClient.SqlCommand et System.Data.SqlClient.SqlDataAdapter
Dans certains cas, cette règle peut ne pas déterminer la valeur d’une chaîne au moment de la compilation, même si vous le pouvez. Dans ce cas, cette règle produit des faux positifs lors de l’utilisation de ces chaînes en tant que commandes SQL. Voici un exemple de chaîne de ce type.
int x = 10;
string query = "SELECT TOP " + x.ToString() + " FROM Table";
La même chose s’applique lors de l’utilisation de ToString()
implicitement.
int x = 10;
string query = String.Format("SELECT TOP {0} FROM Table", x);
Comment corriger les violations
Pour corriger une violation de cette règle, utilisez une requête paramétrable.
Quand supprimer les avertissements
Il est sûr de supprimer un avertissement de cette règle si le texte de la commande ne contient aucune entrée utilisateur.
Supprimer un avertissement
Si vous voulez supprimer une seule violation, ajoutez des directives de préprocesseur à votre fichier source pour désactiver et réactiver la règle.
#pragma warning disable CA2100
// The code that's violating the rule is on this line.
#pragma warning restore CA2100
Pour désactiver la règle sur un fichier, un dossier ou un projet, définissez sa gravité sur none
dans le fichier de configuration.
[*.{cs,vb}]
dotnet_diagnostic.CA2100.severity = none
Pour plus d’informations, consultez Comment supprimer les avertissements de l’analyse de code.
Configurer le code à analyser
Utilisez l’option suivante pour configurer les parties de votre codebase sur lesquelles exécuter cette règle.
Vous pouvez configurer ces options pour cette règle uniquement, pour toutes les règles auxquelles elles s’appliquent ou pour toutes les règles de cette catégorie (Sécurité) auxquelles elles s’appliquent. Pour plus d’informations, consultez Options de configuration des règles de qualité du code.
Exclure des symboles spécifiques
Vous pouvez exclure de l’analyse des symboles spécifiques, comme des types et des méthodes. Par exemple, pour spécifier que la règle ne doit pas s’exécuter sur du code dans des types nommés MyType
, ajoutez la paire clé-valeur suivante à un fichier .editorconfig dans votre projet :
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType
Formats de nom de symbole autorisés dans la valeur d’option (séparés par |
) :
- Nom du symbole uniquement (inclut tous les symboles avec le nom, quel que soit le type ou l’espace de noms qui les contient).
- Noms qualifiés complets au format d’ID de documentation du symbole. Chaque nom de symbole nécessite un préfixe de type symbole, comme
M:
pour les méthodes,T:
pour les types etN:
pour les espaces de noms. .ctor
pour les constructeurs et.cctor
pour les constructeurs statiques.
Exemples :
Valeur d’option | Récapitulatif |
---|---|
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType |
Correspond à tous les symboles nommés MyType . |
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType1|MyType2 |
Correspond à tous les symboles nommés MyType1 ou MyType2 . |
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS.MyType.MyMethod(ParamType) |
Correspond à une méthode MyMethod spécifique avec la signature complète spécifiée. |
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS1.MyType1.MyMethod1(ParamType)|M:NS2.MyType2.MyMethod2(ParamType) |
Correspond à des méthodes MyMethod1 et MyMethod2 spécifiques avec la signature complète spécifiée. |
Exclure des types spécifiques et leurs types dérivés
Vous pouvez exclure de l’analyse des types spécifiques et leurs types dérivés. Par exemple, pour spécifier que la règle ne doit s’exécuter sur aucune méthode dans des types nommés MyType
et leurs types dérivés, ajoutez la paire clé-valeur suivante à un fichier .editorconfig dans votre projet :
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType
Formats de nom de symbole autorisés dans la valeur d’option (séparés par |
) :
- Nom du type uniquement (inclut tous les types avec le nom, quel que soit le type ou l’espace de noms qui les contient).
- Noms qualifiés complets au format d’ID de documentation du symbole, avec un préfixe
T:
facultatif.
Exemples :
Valeur d’option | Récapitulatif |
---|---|
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType |
Correspond à tous les types nommés MyType et à tous leurs types dérivés. |
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType1|MyType2 |
Correspond à tous les types nommés MyType1 ou MyType2 , et à tous leurs types dérivés. |
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS.MyType |
Correspond à un type MyType spécifique avec un nom complet donné et tous ses types dérivés. |
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS1.MyType1|M:NS2.MyType2 |
Correspond à des types MyType1 ou MyType2 spécifiques avec leur nom complet respectif et tous leurs types dérivés. |
Exemple
L’exemple suivant montre une méthode, UnsafeQuery
, qui enfreint la règle et une méthode, SaferQuery
, qui satisfait à la règle à l’aide d’une chaîne de commande paramétrable.
Imports System
Imports System.Data
Imports System.Data.SqlClient
Namespace ca2100
Public Class SqlQueries
Function UnsafeQuery(connection As String,
name As String, password As String) As Object
Dim someConnection As New SqlConnection(connection)
Dim someCommand As New SqlCommand()
someCommand.Connection = someConnection
someCommand.CommandText = "SELECT AccountNumber FROM Users " &
"WHERE Username='" & name & "' AND Password='" & password & "'"
someConnection.Open()
Dim accountNumber As Object = someCommand.ExecuteScalar()
someConnection.Close()
Return accountNumber
End Function
Function SaferQuery(connection As String,
name As String, password As String) As Object
Dim someConnection As New SqlConnection(connection)
Dim someCommand As New SqlCommand()
someCommand.Connection = someConnection
someCommand.Parameters.Add(
"@username", SqlDbType.NChar).Value = name
someCommand.Parameters.Add(
"@password", SqlDbType.NChar).Value = password
someCommand.CommandText = "SELECT AccountNumber FROM Users " &
"WHERE Username=@username AND Password=@password"
someConnection.Open()
Dim accountNumber As Object = someCommand.ExecuteScalar()
someConnection.Close()
Return accountNumber
End Function
End Class
Class MaliciousCode
Shared Sub Main2100(args As String())
Dim queries As New SqlQueries()
queries.UnsafeQuery(args(0), "' OR 1=1 --", "[PLACEHOLDER]")
' Resultant query (which is always true):
' SELECT AccountNumber FROM Users WHERE Username='' OR 1=1
queries.SaferQuery(args(0), "' OR 1=1 --", "[PLACEHOLDER]")
' Resultant query (notice the additional single quote character):
' SELECT AccountNumber FROM Users WHERE Username=''' OR 1=1 --'
' AND Password='[PLACEHOLDER]'
End Sub
End Class
End Namespace
public class SqlQueries
{
public object UnsafeQuery(
string connection, string name, string password)
{
SqlConnection someConnection = new SqlConnection(connection);
SqlCommand someCommand = new SqlCommand();
someCommand.Connection = someConnection;
someCommand.CommandText = "SELECT AccountNumber FROM Users " +
"WHERE Username='" + name +
"' AND Password='" + password + "'";
someConnection.Open();
object accountNumber = someCommand.ExecuteScalar();
someConnection.Close();
return accountNumber;
}
public object SaferQuery(
string connection, string name, string password)
{
SqlConnection someConnection = new SqlConnection(connection);
SqlCommand someCommand = new SqlCommand();
someCommand.Connection = someConnection;
someCommand.Parameters.Add(
"@username", SqlDbType.NChar).Value = name;
someCommand.Parameters.Add(
"@password", SqlDbType.NChar).Value = password;
someCommand.CommandText = "SELECT AccountNumber FROM Users " +
"WHERE Username=@username AND Password=@password";
someConnection.Open();
object accountNumber = someCommand.ExecuteScalar();
someConnection.Close();
return accountNumber;
}
}
class MaliciousCode
{
static void Main2100(string[] args)
{
SqlQueries queries = new SqlQueries();
queries.UnsafeQuery(args[0], "' OR 1=1 --", "[PLACEHOLDER]");
// Resultant query (which is always true):
// SELECT AccountNumber FROM Users WHERE Username='' OR 1=1
queries.SaferQuery(args[0], "' OR 1=1 --", "[PLACEHOLDER]");
// Resultant query (notice the additional single quote character):
// SELECT AccountNumber FROM Users WHERE Username=''' OR 1=1 --'
// AND Password='[PLACEHOLDER]'
}
}