Come si crea una query SQL con parametri? Perché dovrei?


94

Ho sentito che "tutti" utilizzano query SQL parametrizzate per proteggersi dagli attacchi di SQL injection senza dover convalidare ogni parte dell'input dell'utente.

Come fai a fare questo? Lo ottieni automaticamente quando usi le stored procedure?

Quindi la mia comprensione non è parametrizzata:

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

Questo sarebbe parametrizzato?

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

O devo fare qualcosa di più ampio come questo per proteggermi dall'iniezione SQL?

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

Ci sono altri vantaggi nell'utilizzo di query parametrizzate oltre alle considerazioni sulla sicurezza?

Aggiornamento: questo fantastico articolo è stato collegato in uno dei riferimenti alle domande di Grotok. http://www.sommarskog.se/dynamic_sql.html


Ho trovato scioccante che a quanto pare questa domanda non sia stata posta su Stackoverflow prima. Molto buono!
Tamas Czinege

3
Oh, sì. Formulato in modo molto diverso, ovviamente, ma è così.
Joel Coehoorn

10
È necessario utilizzare una query parametrizzata per impedire a Little Bobby Tables di distruggere i dati. Non ho potuto resistere :)
zendar

4
Cosa c'è di così male nel blocco With?
Lurker Indeed

1
Qualcuno ha una domanda # per la domanda "Cosa c'è di male nel blocco With"?
Jim conta il

Risposte:


77

Il tuo esempio EXEC NON sarebbe parametrizzato. Sono necessarie query parametrizzate (istruzioni preparate in alcuni cerchi) per evitare che input come questo causino danni:

'; DROP TABLE bar; -

Prova a inserirlo nella tua variabile fuz (o non farlo, se apprezzi il tuo tavolo da bar). Sono possibili anche domande più sottili e dannose.

Ecco un esempio di come eseguire i parametri con 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

Alle stored procedure viene talvolta attribuito il merito di impedire l'iniezione di SQL. Tuttavia, la maggior parte delle volte devi ancora chiamarli utilizzando i parametri di query o non aiutano. Se si utilizzano esclusivamente stored procedure , è possibile disattivare le autorizzazioni per SELECT, UPDATE, ALTER, CREATE, DELETE, ecc. (Praticamente tutto tranne EXEC) per l'account utente dell'applicazione e ottenere una protezione in questo modo.


Puoi spiegarlo ulteriormente per cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Bazfavore?
Cary Bondoc

1
@CaryBondoc, cosa vuoi sapere? Quella riga crea un parametro chiamato @Bazche è di tipo a varchar(50)cui è assegnato il valore della Bazstringa.
JB King

potresti anche dire "command.parameters.addiwthvalue (" @ Baz ", 50)"
Gavin Perkins

2
@GavinPerkins Supponendo che dire AddWithValue("@Baz", Baz), si potrebbe fare, ma non si deve , soprattutto perché la conversione dei valori di stringa che mappa per impostazione predefinita per nvarcharal reale varchartipo è uno dei luoghi più comuni che possono innescare gli effetti menzionati in tale collegamento.
Joel Coehoorn

15

Sicuramente l'ultimo, cioè

O devo fare qualcosa di più ampio ...? (Sì, cmd.Parameters.Add())

Le query parametrizzate hanno due vantaggi principali:

  • Sicurezza: è un buon modo per evitare le vulnerabilità di SQL Injection
  • Prestazioni: se si richiama regolarmente la stessa query solo con parametri diversi, una query parametrizzata potrebbe consentire al database di memorizzare nella cache le query, il che rappresenta una notevole fonte di miglioramento delle prestazioni.
  • Extra: non dovrai preoccuparti di problemi di formattazione di data e ora nel codice del tuo database. Allo stesso modo, se il tuo codice verrà mai eseguito su macchine con impostazioni internazionali non inglesi, non avrai problemi con punti decimali / virgole decimali.

5

Vuoi andare con il tuo ultimo esempio poiché questo è l'unico che è veramente parametrizzato. Oltre ai problemi di sicurezza (che sono molto più diffusi di quanto potresti pensare) è meglio lasciare che ADO.NET gestisca la parametrizzazione poiché non puoi essere sicuro se il valore che stai passando richiede virgolette singole attorno ad esso o meno senza ispezionare il Typedi ogni parametro .

[Modifica] Ecco un esempio:

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

3
Attenzione con questo: le stringhe .Net sono unicode, quindi il parametro assumerà NVarChar per impostazione predefinita. Se è davvero una colonna VarChar, ciò può causare grossi problemi di prestazioni.
Joel Coehoorn

2

La maggior parte delle persone lo farebbe tramite una libreria di linguaggi di programmazione lato server, come PDO di PHP o Perl DBI.

Ad esempio, in DOP:

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

Questo si occupa dell'escape dei dati per l'inserimento nel database.

Un vantaggio è che puoi ripetere un inserto molte volte con un'istruzione preparata, ottenendo un vantaggio in termini di velocità.

Ad esempio, nella query precedente potrei preparare l'istruzione una volta, quindi eseguire il ciclo creando l'array di dati da un gruppo di dati e ripetere il -> eseguire tutte le volte necessarie.


1

Il testo del comando deve essere come:

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

cmdText = "EXEC foo_from_baz ?"

Quindi aggiungi i valori dei parametri. In questo modo si assicura che il valore con finisca per essere usato come valore, mentre con l'altro metodo se la variabile fuz è impostata su

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

riesci a vedere cosa potrebbe succedere?


0

Ecco una breve lezione per iniziare con SQL e puoi creare da lì e aggiungere alla 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.