Comment créer une requête SQL paramétrée? Pourquoi devrais-je?

94

J'ai entendu dire que «tout le monde» utilise des requêtes SQL paramétrées pour se protéger contre les attaques par injection SQL sans avoir à valider chaque élément d'entrée utilisateur.

Comment est-ce que tu fais ça? Obtenez-vous cela automatiquement lorsque vous utilisez des procédures stockées?

Donc, je comprends que cela n'est pas paramétré:

cmdText = String.Format("SELECT foo FROM bar WHERE baz = '{0}'", fuz)

Serait-ce paramétré?

cmdText = String.Format("EXEC foo_from_baz '{0}'", fuz)

Ou dois-je faire quelque chose de plus complet comme celui-ci pour me protéger de l'injection SQL?

With command
    .Parameters.Count = 1
    .Parameters.Item(0).ParameterName = "@baz"
    .Parameters.Item(0).Value = fuz
End With

Existe-t-il d'autres avantages à utiliser des requêtes paramétrées en plus des considérations de sécurité?

Mise à jour: Cet article génial était lié à l'une des références de questions de Grotok. http://www.sommarskog.se/dynamic_sql.html

Jim compte
la source
J'ai trouvé choquant qu'apparemment, cette question n'ait pas été posée sur Stackoverflow auparavant. Très bon!
Tamas Czinege
3
Oh, c'est vrai. Formulé très différemment, bien sûr, mais c'est le cas.
Joel Coehoorn le
10
Vous devez utiliser une requête paramétrée pour empêcher Little Bobby Tables de détruire vos données. Je
n'ai
4
Qu'y a-t-il de si mauvais dans le bloc With?
Lurker Indeed
1
Quelqu'un a-t-il une question # pour la question "Qu'est-ce qui est si mauvais dans le bloc With"?
Jim compte le

Réponses:

77

Votre exemple EXEC ne sera PAS paramétré. Vous avez besoin de requêtes paramétrées (instructions préparées dans certains cercles) pour empêcher une entrée comme celle-ci de causer des dommages:

'; DROP TABLE bar; -

Essayez de mettre cela dans votre variable fuz (ou non, si vous appréciez votre table de bar). Des requêtes plus subtiles et dommageables sont également possibles.

Voici un exemple de la façon dont vous faites des paramètres avec Sql Server:

Public Function GetBarFooByBaz(ByVal Baz As String) As String
    Dim sql As String = "SELECT foo FROM bar WHERE baz= @Baz"

    Using cn As New SqlConnection("Your connection string here"), _
        cmd As New SqlCommand(sql, cn)

        cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz
        Return cmd.ExecuteScalar().ToString()
    End Using
End Function

Les procédures stockées sont parfois reconnues pour empêcher l'injection SQL. Cependant, la plupart du temps, vous devez toujours les appeler en utilisant des paramètres de requête ou ils n'aident pas. Si vous utilisez exclusivement des procédures stockées , vous pouvez désactiver les autorisations pour SELECT, UPDATE, ALTER, CREATE, DELETE, etc. (à peu près tout sauf EXEC) pour le compte d'utilisateur de l'application et obtenir une certaine protection de cette façon.

Joel Coehoorn
la source
Pouvez-vous expliquer cela plus en détail cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Bazs'il vous plaît?
Cary Bondoc
1
@CaryBondoc, que voulez-vous savoir? Cette ligne crée un paramètre appelé @Bazqui est de type varchar(50)auquel la valeur de la Bazchaîne est affectée .
JB King
vous pouvez aussi dire "command.parameters.addiwthvalue (" @ Baz ", 50)"
Gavin Perkins
2
@GavinPerkins En supposant que vous vouliez dire AddWithValue("@Baz", Baz), vous pouvez le faire, mais vous ne devriez pas , en particulier parce que la conversion des valeurs de chaîne qui correspondent par défaut au type nvarcharréel varcharest l'un des endroits les plus courants qui peuvent déclencher les effets mentionnés dans ce lien.
Joel Coehoorn
15

Certainement le dernier, c'est à dire

Ou dois-je faire quelque chose de plus étendu ...? (Oui, cmd.Parameters.Add())

Les requêtes paramétrées présentent deux avantages principaux:

  • Sécurité: c'est un bon moyen d'éviter les vulnérabilités d' injection SQL
  • Performances: si vous appelez régulièrement la même requête avec des paramètres différents, une requête paramétrée peut permettre à la base de données de mettre en cache vos requêtes, ce qui est une source considérable de gain de performances.
  • Extra: vous n'aurez pas à vous soucier des problèmes de formatage de la date et de l'heure dans le code de votre base de données. De même, si votre code s'exécute un jour sur des machines avec des paramètres régionaux non anglais, vous n'aurez pas de problèmes avec les points décimaux / virgules décimales.
Tamas Czinege
la source
5

Vous voulez aller avec votre dernier exemple car c'est le seul qui est vraiment paramétré. Outre les problèmes de sécurité (qui sont beaucoup plus répandus que vous pourriez le penser), il est préférable de laisser ADO.NET gérer le paramétrage car vous ne pouvez pas être sûr si la valeur que vous passez nécessite des guillemets simples autour d'elle ou non sans inspecter le Typede chaque paramètre .

[Modifier] Voici un exemple:

SqlCommand command = new SqlCommand(
    "select foo from bar where baz = @baz",
    yourSqlConnection
);

SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "@baz";
parameter.Value = "xyz";

command.Parameters.Add(parameter);
Andrew Hare
la source
3
Faites attention à ceci: les chaînes .Net sont unicode, et donc le paramètre prendra NVarChar par défaut. S'il s'agit vraiment d'une colonne VarChar, cela peut entraîner de gros problèmes de performances.
Joel Coehoorn
2

La plupart des gens le feraient via une bibliothèque de langage de programmation côté serveur, comme PDO de PHP ou Perl DBI.

Par exemple, en AOP:

$dbh=pdo_connect(); //you need a connection function, returns a pdo db connection

$sql='insert into squip values(null,?,?)';

$statement=$dbh->prepare($sql);

$data=array('my user supplied data','more stuff');

$statement->execute($data);

if($statement->rowCount()==1){/*it worked*/}

Cela prend soin d'échapper vos données pour l'insertion de la base de données.

Un avantage est que vous pouvez répéter une insertion plusieurs fois avec une instruction préparée, ce qui vous permet de gagner en vitesse.

Par exemple, dans la requête ci-dessus, je pourrais préparer l'instruction une fois, puis effectuer une boucle sur la création du tableau de données à partir d'un ensemble de données et répéter le -> exécuter autant de fois que nécessaire.

JAL
la source
1

Le texte de votre commande doit être comme:

cmdText = "SELECT foo FROM bar WHERE baz = ?"

cmdText = "EXEC foo_from_baz ?"

Ajoutez ensuite les valeurs des paramètres. De cette façon, on s'assure que la valeur con ne sera utilisée que comme valeur, alors qu'avec l'autre méthode si la variable fuz est définie sur

"x'; delete from foo where 'a' = 'a"

pouvez-vous voir ce qui pourrait arriver?

Tony Andrews
la source
0

Voici une courte classe pour commencer avec SQL et vous pouvez construire à partir de là et ajouter à la classe.

MySQL

Public Class mysql

    'Connection string for mysql
    Public SQLSource As String = "Server=123.456.789.123;userid=someuser;password=somesecurepassword;database=somedefaultdatabase;"

    'database connection classes

    Private DBcon As New MySqlConnection
    Private SQLcmd As MySqlCommand
    Public DBDA As New MySqlDataAdapter
    Public DBDT As New DataTable
    Public BindSource As New BindingSource
    ' parameters
    Public Params As New List(Of MySqlParameter)

    ' some stats
    Public RecordCount As Integer
    Public Exception As String

    Function ExecScalar(SQLQuery As String) As Long
        Dim theID As Long
        DBcon.ConnectionString = SQLSource
        Try
            DBcon.Open()
            SQLcmd = New MySqlCommand(SQLQuery, DBcon)
            'loads params into the query
            Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))

            'or like this is also good
            'For Each p As MySqlParameter In Params
            ' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
            ' Next
            ' clears params
            Params.Clear()
            'return the Id of the last insert or result of other query
            theID = Convert.ToInt32(SQLcmd.ExecuteScalar())
            DBcon.Close()

        Catch ex As MySqlException
            Exception = ex.Message
            theID = -1
        Finally
            DBcon.Dispose()
        End Try
        ExecScalar = theID
    End Function

    Sub ExecQuery(SQLQuery As String)

        DBcon.ConnectionString = SQLSource
        Try
            DBcon.Open()
            SQLcmd = New MySqlCommand(SQLQuery, DBcon)
            'loads params into the query
            Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))

            'or like this is also good
            'For Each p As MySqlParameter In Params
            ' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
            ' Next
            ' clears params

            Params.Clear()
            DBDA.SelectCommand = SQLcmd
            DBDA.Update(DBDT)
            DBDA.Fill(DBDT)
            BindSource.DataSource = DBDT  ' DBDT will contain your database table with your records
            DBcon.Close()
        Catch ex As MySqlException
            Exception = ex.Message
        Finally
            DBcon.Dispose()
        End Try
    End Sub
    ' add parameters to the list
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New MySqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub
End Class

MS SQL / Express

Public Class MSSQLDB
    ' CREATE YOUR DB CONNECTION
    'Change the datasource
    Public SQLSource As String = "Data Source=someserver\sqlexpress;Integrated Security=True"
    Private DBCon As New SqlConnection(SQLSource)

    ' PREPARE DB COMMAND
    Private DBCmd As SqlCommand

    ' DB DATA
    Public DBDA As SqlDataAdapter
    Public DBDT As DataTable

    ' QUERY PARAMETERS
    Public Params As New List(Of SqlParameter)

    ' QUERY STATISTICS
    Public RecordCount As Integer
    Public Exception As String

    Public Sub ExecQuery(Query As String, Optional ByVal RunScalar As Boolean = False, Optional ByRef NewID As Long = -1)
        ' RESET QUERY STATS
        RecordCount = 0
        Exception = ""
        Dim RunScalar As Boolean = False

        Try
            ' OPEN A CONNECTION
            DBCon.Open()

            ' CREATE DB COMMAND
            DBCmd = New SqlCommand(Query, DBCon)

            ' LOAD PARAMS INTO DB COMMAND
            Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))

            ' CLEAR PARAMS LIST
            Params.Clear()

            ' EXECUTE COMMAND & FILL DATATABLE
            If RunScalar = True Then
                NewID = DBCmd.ExecuteScalar()
            End If
            DBDT = New DataTable
            DBDA = New SqlDataAdapter(DBCmd)
            RecordCount = DBDA.Fill(DBDT)
        Catch ex As Exception
            Exception = ex.Message
        End Try


        ' CLOSE YOUR CONNECTION
        If DBCon.State = ConnectionState.Open Then DBCon.Close()
    End Sub

    ' INCLUDE QUERY & COMMAND PARAMETERS
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New SqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub
End Class
Chillzy
la source