Warum bevorzugen wir immer die Verwendung von Parametern in SQL-Anweisungen?

114

Ich bin sehr neu in der Arbeit mit Datenbanken. Jetzt kann ich schreiben SELECT, UPDATE, DELETE, und INSERTBefehle. Aber ich habe viele Foren gesehen, in denen wir lieber schreiben:

SELECT empSalary from employee where salary = @salary

...anstatt:

SELECT empSalary from employee where salary = txtSalary.Text

Warum bevorzugen wir immer Parameter und wie würde ich sie verwenden?

Ich wollte die Verwendung und die Vorteile der ersten Methode kennen. Ich habe sogar von SQL Injection gehört, aber ich verstehe es nicht ganz. Ich weiß nicht einmal, ob SQL Injection mit meiner Frage zusammenhängt.

Sandig
quelle
2
Sie haben Recht, dies hängt mit der SQL-Injection zusammen. Der Umgang mit Parametern liegt normalerweise in der Verantwortung der Sprache / des Frameworks, in dem Ihr Programm ausgeführt wird, und kann sprachabhängig sein. Bitte posten Sie sowohl Ihr RDBMS (hilfreich) als auch Ihr ORM-Rahmenwerk (erforderlich).
Uhrwerk-Muse
1
Ich verwende C # als Programmiersprache und SQL Server 2008 als Datenbank. Ich verwende Microsoft dotNet Framework 4.0. Es tut mir wirklich sehr leid, dass ich nicht sicher bin, was Sie fragen (RDBMS oder ORM). Vielleicht können Sie mir jetzt meine RDBMS- und ORM-Framework-Versionen geben :-). Vielen Dank
Sandy
1
RDBMS ist Ihre Datenbank, in Ihrem Fall SQL Server 2008. Ihr ORM ist die Methode, mit der Sie auf Ihre Datenbank zugreifen, in diesem Fall ADO.NET. Andere umfassen LINQ to SQL und Entity Framework . Sobald Sie die Grundlagen von ADO.NET und SQL erlernt haben, empfehle ich die Verwendung eines ORM wie LINQ oder EF, da diese viele der Probleme beheben, auf die Sie beim manuellen Schreiben von SQL stoßen würden.
Chad Levy

Antworten:

128

Durch die Verwendung von Parametern können SQL Injection-Angriffe verhindert werden, wenn die Datenbank in Verbindung mit einer Programmschnittstelle wie einem Desktop-Programm oder einer Website verwendet wird.

In Ihrem Beispiel kann ein Benutzer SQL-Code direkt in Ihrer Datenbank ausführen, indem er Anweisungen in erstellt txtSalary.

Wenn sie beispielsweise schreiben würden 0 OR 1=1, wäre das ausgeführte SQL

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

wobei alle empSalaries zurückgegeben würden.

Außerdem könnte ein Benutzer weitaus schlechtere Befehle für Ihre Datenbank ausführen, einschließlich des Löschens, wenn er Folgendes geschrieben hat 0; Drop Table employee:

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

Die Tabelle employeewürde dann gelöscht.


In Ihrem Fall scheint es, als würden Sie .NET verwenden. Die Verwendung von Parametern ist so einfach wie:

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

Bearbeiten 2016-4-25:

Gemäß dem Kommentar von George Stocker habe ich den Beispielcode so geändert, dass er nicht verwendet wird AddWithValue. Außerdem wird allgemein empfohlen, IDisposables in usingAnweisungen einzuschließen .

Chad Levy
quelle
tolle Lösung. Aber können Sie etwas mehr erklären, warum und wie die Verwendung von Parametern sicher ist? Ich meine, es sieht immer noch so aus, als ob der Befehl sql derselbe sein wird
Sandy
Können wir dem Befehl sql mehrere Parameter hinzufügen? Wie benötigen wir in einem INSERT-Befehl?
Sandy
2
SQL Server behandelt den Text in den Parametern nur als Eingabe und führt ihn niemals aus.
Chad Levy
3
Ja, Sie können mehrere Parameter hinzufügen : Insert Into table (Col1, Col2) Values (@Col1, @Col2). In Ihrem Code würden Sie mehrere AddWithValues hinzufügen .
Chad Levy
1
Bitte verwenden Sie nicht AddWithValue! Dies kann implizite Konvertierungsprobleme verursachen. Stellen Sie die Größe immer explizit ein und fügen Sie den Parameterwert mit hinzu parameter.Value = someValue.
George Stocker
74

Sie haben Recht, dies hängt mit der SQL-Injection zusammen , einer Sicherheitsanfälligkeit, die es einem Malicioius-Benutzer ermöglicht, beliebige Anweisungen für Ihre Datenbank auszuführen. Dieser alte Lieblings- XKCD-Comic veranschaulicht das Konzept:

Ihre Tochter heißt Help I'm und ist in einer Führerscheinfabrik gefangen.


Wenn Sie in Ihrem Beispiel nur Folgendes verwenden:

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

Sie sind offen für SQL-Injection. Angenommen, jemand gibt txtSalary ein:

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

Wenn Sie diese Abfrage ausführen, führt sie ein SELECTund ein UPDATEoder DROPoder was auch immer sie wollten aus. Das --am Ende kommentiert einfach den Rest Ihrer Abfrage aus, was für den Angriff nützlich wäre, wenn Sie danach etwas verketten würden txtSalary.Text.


Der richtige Weg ist die Verwendung parametrisierter Abfragen, z. B. (C #):

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

Damit können Sie die Abfrage sicher ausführen.

Informationen zum Vermeiden von SQL-Injection in mehreren anderen Sprachen finden Sie unter bobby-tables.com , einer Website, die von einem SO-Benutzer verwaltet wird .

NullUserException
quelle
1
tolle Lösung. Aber können Sie etwas mehr erklären, warum und wie die Verwendung von Parametern sicher ist? Ich meine, es sieht immer noch so aus, als ob der Befehl sql derselbe sein wird.
Sandy
1
@ user815600: ein verbreiteter Irrtum - Sie immer noch glauben , dass die Abfrage mit Parametern im Wert nehmen und ersetzt die Parameter für den Ist - Wert - nicht wahr? Nein, das passiert nicht! - Stattdessen wird die SQL-Anweisung mit den Parametern zusammen mit einer Liste der Parameter und ihrer Werte an SQL Server übertragen. - Die SQL-Anweisung wird nicht dieselbe sein.
marc_s
1
Das bedeutet, dass die SQL-Injektion durch den internen Mechanismus oder die Sicherheit des SQL-Servers überwacht wird. Vielen Dank.
Sandy
4
So sehr ich Cartoons mag, wenn Sie Ihren Code mit ausreichenden Berechtigungen zum Löschen von Tabellen ausführen, haben Sie wahrscheinlich größere Probleme.
Philw
9

Zusätzlich zu anderen Antworten muss hinzugefügt werden, dass Parameter nicht nur dazu beitragen, die SQL-Injektion zu verhindern, sondern auch die Leistung von Abfragen verbessern können . SQL Server-Caching parametrisierte Abfragepläne und Wiederverwendung bei wiederholter Ausführung von Abfragen. Wenn Sie Ihre Abfrage nicht parametrisiert haben, würde der SQL Server bei jeder Abfrageausführung (mit einigen Ausschlüssen) einen neuen Plan erstellen, wenn der Text der Abfrage unterschiedlich wäre.

Weitere Informationen zum Zwischenspeichern von Abfrageplänen

Oleg
quelle
1
Dies ist relevanter als man denkt. Sogar eine "kleine" Abfrage kann tausend- oder millionenfach ausgeführt werden, wodurch der gesamte Abfragecache effektiv geleert wird.
James
5

Zwei Jahre nach meinem ersten Versuch rezidiviere ich ...

Warum bevorzugen wir Parameter? SQL-Injection ist natürlich ein großer Grund, aber könnte es sein, dass wir uns insgeheim danach sehnen, wieder zu SQL als Sprache zurückzukehren . SQL in String-Literalen ist bereits eine seltsame kulturelle Praxis, aber zumindest können Sie Ihre Anfrage kopieren und in das Management Studio einfügen. SQL, das dynamisch mit Bedingungen und Kontrollstrukturen in der Hostsprache erstellt wurde, wenn SQL über Bedingungen und Kontrollstrukturen verfügt, ist nur eine Barbarei der Stufe 0. Sie müssen Ihre App im Debug oder mit einem Trace ausführen, um zu sehen, welche SQL sie generiert.

Hören Sie nicht nur mit Parametern auf. Gehen Sie den ganzen Weg und verwenden Sie QueryFirst (Haftungsausschluss: den ich geschrieben habe). Ihr SQL befindet sich in einer SQL-Datei. Sie bearbeiten es im fabelhaften TSQL-Editorfenster mit Syntaxüberprüfung und Intellisense für Ihre Tabellen und Spalten. Sie können Testdaten im Bereich für spezielle Kommentare zuweisen und auf "Wiedergabe" klicken, um Ihre Abfrage direkt im Fenster auszuführen. Das Erstellen eines Parameters ist so einfach wie das Einfügen von "@myParam" in Ihre SQL. Bei jedem Speichern generiert QueryFirst dann den C # -Wrapper für Ihre Abfrage. Ihre Parameter werden stark typisiert als Argumente für die Execute () -Methoden angezeigt. Ihre Ergebnisse werden in einer IEnumerable oder Liste stark typisierter POCOs zurückgegeben, den Typen, die aus dem von Ihrer Abfrage zurückgegebenen tatsächlichen Schema generiert wurden. Wenn Ihre Abfrage nicht ausgeführt wird, wird Ihre App nicht kompiliert. Wenn sich Ihr Datenbankschema ändert und Ihre Abfrage ausgeführt wird, aber einige Spalten verschwinden, zeigt der Kompilierungsfehler auf die Zeile in Ihrem Codedas versucht, auf die fehlenden Daten zuzugreifen. Und es gibt zahlreiche weitere Vorteile. Warum möchten Sie auf andere Weise auf Daten zugreifen?

bbsimonbb
quelle
4

Wenn in SQL ein Wort ein @ -Zeichen enthält, bedeutet dies, dass es eine Variable ist. Wir verwenden diese Variable, um den Wert darin festzulegen und sie im Zahlenbereich desselben SQL-Skripts zu verwenden, da sie nur auf das einzelne Skript beschränkt ist, während Sie viele Variablen deklarieren können vom gleichen Typ und Namen auf vielen Skripten. Wir verwenden diese Variable in gespeicherten Prozeduren, da gespeicherte Prozeduren vorkompilierte Abfragen sind und wir Werte in dieser Variablen von Skript, Desktop und Websites übergeben können. Weitere Informationen finden Sie unter Deklarieren lokaler Variablen , gespeicherte SQL-Prozeduren und SQL-Injektionen .

Lesen Sie auch Schutz vor SQL-Injection. Hier erfahren Sie, wie Sie Ihre Datenbank schützen können.

Hoffe es hilft dir auch jede Frage zu verstehen, kommentiere mich.

Emaad Ali
quelle
3

Andere Antworten behandeln, warum Parameter wichtig sind, aber es gibt einen Nachteil! In .net gibt es verschiedene Methoden zum Erstellen von Parametern (Add, AddWithValue), bei denen Sie sich jedoch unnötigerweise um den Parameternamen kümmern müssen, und alle beeinträchtigen die Lesbarkeit von SQL im Code. Wenn Sie versuchen, über SQL zu meditieren, müssen Sie oben oder unten herumjagen, um zu sehen, welcher Wert im Parameter verwendet wurde.

Ich behaupte demütig, dass meine kleine SqlBuilder-Klasse die eleganteste Art ist, parametrisierte Abfragen zu schreiben . Ihr Code wird so aussehen ...

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();

Ihr Code wird kürzer und viel besser lesbar sein. Sie brauchen nicht einmal zusätzliche Zeilen, und wenn Sie zurücklesen, müssen Sie nicht nach dem Wert von Parametern suchen. Die Klasse, die Sie brauchen, ist hier ...

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
quelle
Die Benennung Ihrer Parameter hilft sicherlich bei der Profilerstellung der Abfragen, die der Server ausführt.
Dave R.
Mein Chef sagte dasselbe. Wenn Ihnen aussagekräftige Parameternamen wichtig sind, fügen Sie der value-Methode ein paramName-Argument hinzu. Ich vermute, Sie komplizieren die Dinge unnötig.
Bbsimonbb
Schlechte Idee. Wie bereits erwähnt, AddWithValuekann dies zu impliziten Konvertierungsproblemen führen.
Adam Calvet Bohl
@Adam Sie haben Recht, aber das hindert AddWithValue () nicht daran, sehr weit verbreitet zu sein, und ich denke nicht, dass dies die Idee ungültig macht. Aber in der Zwischenzeit habe ich mir eine viel bessere Methode zum Schreiben parametrisierter Abfragen ausgedacht, und das verwendet AddWithValue () nicht :-)
bbsimonbb
Richtig! Versprich mir, dass ich mir das bald ansehen werde!
Adam Calvet Bohl
3

Alte Post, wollte aber sicherstellen, dass Neuankömmlinge über gespeicherte Verfahren informiert sind .

Mein 10 ¢ Wert hier ist, dass, wenn Sie Ihre SQL-Anweisung als gespeicherte Prozedur schreiben können , dies meiner Ansicht nach der optimale Ansatz ist. Ich IMMER verwenden gespeicherte Prozeduren und nie Schleife durch Aufzeichnungen in meinem Haupt - Code. Zum Beispiel : SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Wenn Sie gespeicherte Prozeduren verwenden, können Sie den Benutzer nur auf die EXECUTE- Berechtigung beschränken, wodurch Sicherheitsrisiken verringert werden .

Ihre gespeicherte Prozedur ist von Natur aus paramerisiert, und Sie können Eingabe- und Ausgabeparameter angeben.

Auf die gespeicherte Prozedur (wenn sie Daten über eine SELECTAnweisung zurückgibt ) kann genauso zugegriffen und gelesen werden wie über eine reguläre SELECTAnweisung in Ihrem Code.

Es läuft auch schneller, da es auf dem SQL Server kompiliert wird.

Habe ich auch erwähnt, dass Sie mehrere Schritte ausführen können, z. B. updateeine Tabelle, Werte auf einem anderen DB-Server überprüfen und dann nach Abschluss Daten an den Client zurückgeben können, alle auf demselben Server und ohne Interaktion mit dem Client. Das ist also VIEL schneller als das Codieren dieser Logik in Ihrem Code.

Arvin Amir
quelle