Pourquoi préférons-nous toujours utiliser des paramètres dans les instructions SQL?

114

Je suis très novice dans le domaine des bases de données. Maintenant , je peux écrire SELECT, UPDATE, DELETEet les INSERTcommandes. Mais j'ai vu de nombreux forums où nous préférons écrire:

SELECT empSalary from employee where salary = @salary

...au lieu de:

SELECT empSalary from employee where salary = txtSalary.Text

Pourquoi préférons-nous toujours utiliser des paramètres et comment les utiliserais-je?

Je voulais connaître l'utilisation et les avantages de la première méthode. J'ai même entendu parler de l'injection SQL mais je ne la comprends pas entièrement. Je ne sais même pas si l'injection SQL est liée à ma question.

Sablonneux
la source
2
Vous avez raison, c'est lié à l'injection SQL. La manière de gérer les paramètres est généralement de la responsabilité de la langue / structure exécutée par votre programme et peut dépendre de la langue. Veuillez publier à la fois votre SGBDR (utile) et le cadre ORM (nécessaire).
Clockwork-Muse
1
J'utilise C # comme langage de programmation et Sql Server 2008 comme base de données. J'utilise Microsoft dotNet framework 4.0. Je suis vraiment désolé, je ne suis pas sûr de ce que vous demandez (SGBDR ou ORM), peut-être pouvez-vous me donner mes versions de framework SGBDR et ORM maintenant :-). Merci beaucoup
Sandy
1
SGBDR est votre base de données, dans votre cas SQL Server 2008. Votre ORM est la méthode par laquelle vous accédez à votre base de données, dans ce cas, ADO.NET. D'autres incluent LINQ to SQL et Entity Framework . En fait, une fois que vous avez appris les bases d'ADO.NET et de SQL, je vous recommande d'utiliser un ORM tel que LINQ ou EF, car ils prennent en charge de nombreux problèmes que vous rencontrez en écrivant manuellement SQL.
Chad Levy

Réponses:

129

L'utilisation de paramètres permet d'éviter les attaques par injection SQL lorsque la base de données est utilisée conjointement avec une interface de programme telle qu'un programme de bureau ou un site Web.

Dans votre exemple, un utilisateur peut exécuter directement du code SQL sur votre base de données en créant des instructions dans txtSalary.

Par exemple, s'ils devaient écrire 0 OR 1=1, le SQL exécuté serait

 SELECT empSalary from employee where salary = 0 or 1=1

par lequel tous les salaires seraient retournés.

De plus, un utilisateur pourrait exécuter des commandes bien pires contre votre base de données, y compris la supprimer s'il écrivait 0; Drop Table employee:

SELECT empSalary from employee where salary = 0; Drop Table employee

Le tableau employeeserait alors supprimé.


Dans votre cas, il semble que vous utilisez .NET. L'utilisation des paramètres est aussi simple que:

C #

string sql = "SELECT empSalary from employee where salary = @salary";

using (SqlConnection connection = new SqlConnection(/* connection info */))
using (SqlCommand command = new SqlCommand(sql, connection))
{
    var salaryParam = new SqlParameter("salary", SqlDbType.Money);
    salaryParam.Value = txtMoney.Text;

    command.Parameters.Add(salaryParam);
    var results = command.ExecuteReader();
}

VB.NET

Dim sql As String = "SELECT empSalary from employee where salary = @salary"
Using connection As New SqlConnection("connectionString")
    Using command As New SqlCommand(sql, connection)
        Dim salaryParam = New SqlParameter("salary", SqlDbType.Money)
        salaryParam.Value = txtMoney.Text

        command.Parameters.Add(salaryParam)

        Dim results = command.ExecuteReader()
    End Using
End Using

Modifier le 25/04/2016:

Selon le commentaire de George Stocker, j'ai changé l'exemple de code pour ne pas l'utiliser AddWithValue. En outre, il est généralement recommandé d'encapsuler les IDisposables dans des usinginstructions.

Chad Levy
la source
excellente solution. Mais pouvez-vous expliquer un peu plus pourquoi et comment l'utilisation des paramètres est sûre. Je veux dire, il semble toujours que la commande sql sera la même
Sandy
pouvons-nous ajouter plusieurs paramètres à la commande sql. Comme nous pouvons l'exiger dans une commande INSERT?
Sandy
2
SQL Server traite le texte à l'intérieur des paramètres uniquement comme une entrée et ne l'exécutera jamais.
Chad Levy
3
Oui, vous pouvez ajouter plusieurs paramètres: Insert Into table (Col1, Col2) Values (@Col1, @Col2). Dans votre code, vous ajouteriez plusieurs AddWithValues.
Chad Levy
1
Veuillez ne pas utiliser AddWithValue! Cela peut entraîner des problèmes de conversion implicites. Définissez toujours la taille explicitement et ajoutez la valeur du paramètre avec parameter.Value = someValue.
George Stocker
75

Vous avez raison, cela est lié à l' injection SQL , qui est une vulnérabilité qui permet à un utilisateur malveillant d'exécuter des déclarations arbitraires sur votre base de données. Cette bande dessinée XKCD préférée de l'ancien temps illustre le concept:

Sa fille s'appelle Help I'm piégé dans une usine de permis de conduire.


Dans votre exemple, si vous utilisez simplement:

var query = "SELECT empSalary from employee where salary = " + txtSalary.Text;
// and proceed to execute this query

Vous êtes ouvert à l'injection SQL. Par exemple, disons que quelqu'un entre txtSalary:

1; UPDATE employee SET salary = 9999999 WHERE empID = 10; --
1; DROP TABLE employee; --
// etc.

Lorsque vous exécutez cette requête, il effectuera un SELECTet un UPDATEou DROP, ou ce qu'ils voulaient. Le --à la fin commente simplement le reste de votre requête, ce qui serait utile dans l'attaque si vous concaténiez quoi que ce soit après txtSalary.Text.


La bonne façon est d'utiliser des requêtes paramétrées, par exemple (C #):

SqlCommand query =  new SqlCommand("SELECT empSalary FROM employee 
                                    WHERE salary = @sal;");
query.Parameters.AddWithValue("@sal", txtSalary.Text);

Avec cela, vous pouvez exécuter la requête en toute sécurité.

Pour savoir comment éviter l'injection SQL dans plusieurs autres langues, consultez bobby-tables.com , un site Web géré par un utilisateur SO .

NullUserException
la source
1
excellente solution. Mais pouvez-vous expliquer un peu plus pourquoi et comment l'utilisation des paramètres est sûre. Je veux dire qu'il semble toujours que la commande sql sera la même.
Sandy
1
@ user815600: une idée fausse courante - vous pensez toujours que la requête avec des paramètres prendra la valeur et remplacera les paramètres par les valeurs réelles - n'est-ce pas? Non, cela ne se produit pas! - au lieu de cela, l'instruction SQL avec les paramètres sera transmise à SQL Server, avec une liste de paramètres et leurs valeurs - l'instruction SQL ne sera pas la même
marc_s
1
cela signifie que l'injection SQL est surveillée par le mécanisme interne ou la sécurité du serveur SQL. Merci.
Sandy
4
Tout comme j'aime les dessins animés, si vous exécutez votre code avec des privilèges suffisants pour supprimer des tables, vous avez probablement des problèmes plus larges.
philw
9

En plus d'autres réponses, il faut ajouter que les paramètres aident non seulement à empêcher l'injection SQL, mais peuvent également améliorer les performances des requêtes . Le serveur SQL met en cache les plans de requêtes paramétrés et les réutilise lors de l'exécution répétée de requêtes. Si vous n'avez pas paramétré votre requête, le serveur sql compilerait un nouveau plan à chaque exécution de requête (avec une certaine exclusion) si le texte de la requête était différent.

Plus d'informations sur la mise en cache du plan de requête

Oleg
la source
1
C'est plus pertinent qu'on ne le pense. Même une «petite» requête peut être exécutée des milliers ou des millions de fois, vidant ainsi l'ensemble du cache de requêtes.
James
5

Deux ans après ma première tentative , je récidive ...

Pourquoi préférons-nous les paramètres? L'injection SQL est évidemment une grande raison, mais se pourrait-il que nous aspirions secrètement à revenir à SQL en tant que langage . SQL dans les chaînes littérales est déjà une pratique culturelle étrange, mais au moins vous pouvez copier et coller votre requête dans le studio de gestion. SQL construit dynamiquement avec des conditions et des structures de contrôle du langage hôte, lorsque SQL a des conditions et des structures de contrôle, est juste une barbarie de niveau 0. Vous devez exécuter votre application en mode débogage, ou avec une trace, pour voir quel SQL elle génère.

Ne vous arrêtez pas uniquement avec des paramètres. Allez jusqu'au bout et utilisez QueryFirst (avertissement: que j'ai écrit). Votre SQL vit dans un fichier .sql. Vous le modifiez dans la fabuleuse fenêtre de l'éditeur TSQL, avec la validation de la syntaxe et Intellisense pour vos tables et colonnes. Vous pouvez attribuer des données de test dans la section des commentaires spéciaux et cliquer sur «jouer» pour exécuter votre requête directement dans la fenêtre. Créer un paramètre est aussi simple que de mettre "@myParam" dans votre SQL. Ensuite, chaque fois que vous enregistrez, QueryFirst génère le wrapper C # pour votre requête. Vos paramètres apparaissent, fortement typés, comme arguments des méthodes Execute (). Vos résultats sont renvoyés dans un IEnumerable ou une liste de POCO fortement typés, les types générés à partir du schéma réel renvoyé par votre requête. Si votre requête ne s'exécute pas, votre application ne sera pas compilée. Si votre schéma de base de données change et que votre requête s'exécute mais que certaines colonnes disparaissent, l'erreur de compilation pointe vers la ligne de votre codequi tente d'accéder aux données manquantes. Et il existe de nombreux autres avantages. Pourquoi voudriez-vous accéder aux données d'une autre manière?

bbsimonbb
la source
4

En SQL, lorsqu'un mot contient le signe @, cela signifie qu'il est variable et nous utilisons cette variable pour définir une valeur et l'utiliser sur la zone numérique du même script sql car il n'est limité que sur le script unique alors que vous pouvez déclarer beaucoup de variables de même type et nom sur de nombreux scripts. Nous utilisons cette variable dans un lot de procédures stockées car les procédures stockées sont des requêtes pré-compilées et nous pouvons transmettre des valeurs dans ces variables à partir du script, du bureau et des sites Web pour plus d'informations, lisez Declare Local Variable , Sql Stored Procedure et SQL injections .

Lisez également Protéger de l'injection SQL, il vous expliquera comment protéger votre base de données.

J'espère que cela vous aidera à comprendre également toute question commentez-moi.

Emaad Ali
la source
3

D'autres réponses expliquent pourquoi les paramètres sont importants, mais il y a un inconvénient! Dans .net, il existe plusieurs méthodes pour créer des paramètres (Add, AddWithValue), mais elles nécessitent toutes que vous vous inquiétiez, inutilement, du nom du paramètre, et elles réduisent toutes la lisibilité du SQL dans le code. Lorsque vous essayez de méditer sur le SQL, vous devez chercher au-dessus ou en dessous pour voir quelle valeur a été utilisée dans le paramètre.

Je prétends humblement que ma petite classe SqlBuilder est le moyen le plus élégant d'écrire des requêtes paramétrées . Votre code ressemblera à ceci ...

C #

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName);
myCommand.CommandText = bldr.ToString();

Votre code sera plus court et beaucoup plus lisible. Vous n'avez même pas besoin de lignes supplémentaires et, lorsque vous relisez, vous n'avez pas besoin de chercher la valeur des paramètres. La classe dont vous avez besoin est ici ...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

public class SqlBuilder
{
private StringBuilder _rq;
private SqlCommand _cmd;
private int _seq;
public SqlBuilder(SqlCommand cmd)
{
    _rq = new StringBuilder();
    _cmd = cmd;
    _seq = 0;
}
public SqlBuilder Append(String str)
{
    _rq.Append(str);
    return this;
}
public SqlBuilder Value(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append(paramName);
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public SqlBuilder FuzzyValue(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append("'%' + " + paramName + " + '%'");
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public override string ToString()
{
    return _rq.ToString();
}
}
bbsimonbb
la source
Nommer vos paramètres aide certainement lors du profilage des requêtes exécutées par le serveur.
Dave R.
Mon patron a dit la même chose. Si des noms de paramètres significatifs sont importants pour vous, ajoutez un argument paramName à la méthode value. Je soupçonne que vous compliquez inutilement les choses.
bbsimonbb
Mauvaise idée. Comme cela a été dit précédemment, AddWithValuepeut provoquer des problèmes de conversion implicites.
Adam Calvet Bohl
@Adam vous avez raison, mais cela n'empêche pas AddWithValue () d'être très largement utilisé, et je ne pense pas que cela invalide l'idée. Mais en attendant, j'ai trouvé une bien meilleure façon d'écrire des requêtes paramétrées, et qui n'utilise pas AddWithValue () :-)
bbsimonbb
Droite! Promesse que je vais bientôt regarder ça!
Adam Calvet Bohl
3

Ancien poste, mais voulait s'assurer que les nouveaux arrivants connaissent les procédures stockées .

Ma valeur de 10 ¢ ici est que si vous êtes capable d'écrire votre instruction SQL en tant que procédure stockée , c'est à mon avis l'approche optimale. J'utilise TOUJOURS des procs stockés et je ne boucle jamais les enregistrements de mon code principal. Par exemple: SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Lorsque vous utilisez des procédures stockées, vous pouvez restreindre l'utilisateur à l' autorisation EXECUTE uniquement, réduisant ainsi les risques de sécurité .

Votre procédure stockée est intrinsèquement paramétrée et vous pouvez spécifier des paramètres d'entrée et de sortie.

La procédure stockée (si elle renvoie des données via une SELECTinstruction) est accessible et lue exactement de la même manière que vous le feriez pour une SELECTinstruction régulière dans votre code.

Il s'exécute également plus rapidement car il est compilé sur le serveur SQL.

Ai-je également mentionné que vous pouvez faire plusieurs étapes, par exemple updateune table, vérifier les valeurs sur un autre serveur de base de données, puis une fois finalement terminé, retourner les données au client, le tout sur le même serveur, et aucune interaction avec le client. C'est donc BEAUCOUP plus rapide que de coder cette logique dans votre code.

Arvin Amir
la source