Wie erstelle ich eine parametrisierte SQL-Abfrage? Warum sollte ich?

93

Ich habe gehört, dass "jeder" parametrisierte SQL-Abfragen verwendet, um sich vor SQL-Injection-Angriffen zu schützen, ohne dass jede Benutzereingabe überprüft werden muss.

Wie machst Du das? Erhalten Sie dies automatisch, wenn Sie gespeicherte Prozeduren verwenden?

Mein Verständnis ist also nicht parametrisiert:

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

Würde dies parametrisiert werden?

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

Oder muss ich etwas umfangreicheres tun, um mich vor SQL-Injection zu schützen?

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

Gibt es neben den Sicherheitsaspekten noch weitere Vorteile bei der Verwendung parametrisierter Abfragen?

Update: Dieser großartige Artikel wurde in einer der Fragen von Grotok verlinkt. http://www.sommarskog.se/dynamic_sql.html

Jim zählt
quelle
Ich fand es schockierend, dass diese Frage anscheinend noch nie bei Stackoverflow gestellt wurde. Sehr gut!
Tamas Czinege
3
Oh, das hat es. Natürlich ganz anders formuliert, aber es hat.
Joel Coehoorn
9
Sie sollten eine parametrisierte Abfrage verwenden, um zu verhindern, dass Little Bobby Tables Ihre Daten zerstört. Konnte nicht widerstehen :)
Zendar
4
Was ist so schlimm am With-Block?
Lurker In der Tat
1
Hat jemand eine Frage # für die Frage "Was ist so schlimm an dem With-Block"?
Jim zählt

Antworten:

76

Ihr EXEC-Beispiel würde NICHT parametrisiert. Sie benötigen parametrisierte Abfragen (in einigen Kreisen vorbereitete Anweisungen), um zu verhindern, dass Eingaben wie diese Schaden anrichten:

'; DROP TABLE bar; -

Versuchen Sie, dies in Ihre Fuz-Variable einzufügen (oder nicht, wenn Sie Ihre Stehtabelle schätzen). Auch subtilere und schädlichere Abfragen sind möglich.

Hier ist ein Beispiel, wie Sie Parameter mit SQL Server ausführen:

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

Gespeicherten Prozeduren wird manchmal die Verhinderung der SQL-Injection zugeschrieben. Meistens müssen Sie sie jedoch immer noch mit Abfrageparametern aufrufen, oder sie helfen nicht. Wenn Sie ausschließlich gespeicherte Prozeduren verwenden , können Sie die Berechtigungen für SELECT, UPDATE, ALTER, CREATE, DELETE usw. (fast alles außer EXEC) für das Anwendungsbenutzerkonto deaktivieren und auf diese Weise einen gewissen Schutz erhalten.

Joel Coehoorn
quelle
Können Sie das cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Bazbitte näher erläutern ?
Cary Bondoc
1
@CaryBondoc, was willst du wissen? Diese Zeile erstellt einen Parameter @Bazvom Typ varchar(50), dem der Wert der BazZeichenfolge zugewiesen wird .
JB King
Sie könnten auch "command.parameters.addiwthvalue (" @ Baz ", 50)" sagen
Gavin Perkins
2
@GavinPerkins Angenommen, Sie meinten AddWithValue("@Baz", Baz), Sie könnten das tun, aber Sie sollten es nicht tun , insbesondere weil das Konvertieren von Zeichenfolgenwerten, die standardmäßig nvarchardem tatsächlichen varcharTyp zugeordnet sind, einer der häufigsten Orte ist, die die in diesem Link genannten Effekte auslösen können.
Joel Coehoorn
15

Auf jeden Fall der letzte, dh

Oder muss ich etwas umfangreicheres tun ...? (Ja cmd.Parameters.Add())

Parametrisierte Abfragen haben zwei Hauptvorteile:

  • Sicherheit: Dies ist ein guter Weg, um SQL Injection- Schwachstellen zu vermeiden
  • Leistung: Wenn Sie dieselbe Abfrage regelmäßig nur mit unterschiedlichen Parametern aufrufen, kann die Datenbank mit einer parametrisierten Abfrage Ihre Abfragen zwischenspeichern, was eine erhebliche Quelle für Leistungssteigerungen darstellt.
  • Extra: Sie müssen sich keine Gedanken über Formatierungsprobleme bei Datum und Uhrzeit in Ihrem Datenbankcode machen. Wenn Ihr Code jemals auf Computern mit einem nicht englischen Gebietsschema ausgeführt wird, haben Sie keine Probleme mit Dezimalstellen / Kommas.
Tamas Czinege
quelle
5

Sie möchten mit Ihrem letzten Beispiel fortfahren, da dies das einzige ist, das wirklich parametrisiert ist. Neben Sicherheitsbedenken (die weitaus häufiger auftreten als Sie vielleicht denken) ist es am besten, ADO.NET die Parametrisierung durchführen zu lassen, da Sie nicht sicher sein können, ob für den übergebenen Wert einfache Anführungszeichen erforderlich sind oder nicht, ohne die Typeeinzelnen Parameter zu überprüfen .

[Bearbeiten] Hier ist ein Beispiel:

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
quelle
3
Beachten Sie Folgendes: .Net-Zeichenfolgen sind Unicode-Zeichenfolgen, sodass der Parameter standardmäßig NVarChar annimmt. Wenn es sich wirklich um eine VarChar-Spalte handelt, kann dies zu großen Leistungsproblemen führen.
Joel Coehoorn
2

Die meisten Leute würden dies über eine serverseitige Programmiersprachenbibliothek tun, wie PHPs PDO oder Perl DBI.

Zum Beispiel in PDO:

$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*/}

Dadurch wird sichergestellt, dass Ihre Daten nicht zum Einfügen in die Datenbank verwendet werden.

Ein Vorteil ist, dass Sie eine Einfügung mit einer vorbereiteten Anweisung viele Male wiederholen können, um einen Geschwindigkeitsvorteil zu erzielen.

Zum Beispiel könnte ich in der obigen Abfrage die Anweisung einmal vorbereiten und dann das Erstellen des Datenarrays aus einer Reihe von Daten durchlaufen und die Ausführung -> so oft wie nötig wiederholen.

JAL
quelle
1

Ihr Befehlstext muss wie folgt aussehen:

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

cmdText = "EXEC foo_from_baz ?"

Fügen Sie dann Parameterwerte hinzu. Auf diese Weise wird sichergestellt, dass der Wert con nur als Wert verwendet wird, während dies bei der anderen Methode der Fall ist, wenn die Variable fuz auf gesetzt ist

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

Kannst du sehen, was passieren könnte?

Tony Andrews
quelle
0

Hier ist eine kurze Klasse, um mit SQL zu beginnen, und Sie können von dort aus erstellen und zur Klasse hinzufügen.

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
quelle