Jak utworzyć sparametryzowane zapytanie SQL? Dlaczego powinienem?


94

Słyszałem, że „wszyscy” używają sparametryzowanych zapytań SQL do ochrony przed atakami typu SQL injection bez konieczności sprawdzania każdego elementu wprowadzanego przez użytkownika.

Jak Ty to robisz? Czy otrzymujesz to automatycznie podczas korzystania z procedur składowanych?

W moim rozumieniu to nie jest sparametryzowane:

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

Czy byłoby to sparametryzowane?

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

A może muszę zrobić coś bardziej rozbudowanego, aby uchronić się przed wstrzyknięciem SQL?

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

Czy poza względami bezpieczeństwa istnieją inne zalety korzystania z zapytań parametrycznych?

Aktualizacja: ten wspaniały artykuł został powiązany w jednym z pytań dotyczących odniesienia przez Grotok. http://www.sommarskog.se/dynamic_sql.html


To szokujące, że najwyraźniej to pytanie nie zostało wcześniej zadane w Stackoverflow. Bardzo dobry!
Tamas Czinege

3
Och, ma. Oczywiście sformułowane bardzo inaczej, ale tak jest.
Joel Coehoorn

10
Powinieneś użyć sparametryzowanego zapytania, aby zapobiec zniszczeniu danych przez Little Bobby Tables . Nie mogłem się oprzeć :)
zendar

4
Co jest takiego złego w bloku With?
Lurker Rzeczywiście,

1
Czy ktoś ma pytanie # na pytanie „Co jest złego w bloku With”?
Jim liczy

Odpowiedzi:


77

Twój przykład EXEC NIE byłby sparametryzowany. Potrzebujesz sparametryzowanych zapytań (przygotowanych instrukcji w niektórych kręgach), aby zapobiec uszkodzeniom takich danych wejściowych:

'; Pasek DROP TABLE; -

Spróbuj umieścić to w zmiennej fuz (lub nie, jeśli cenisz swój stół słupkowy). Możliwe są również bardziej subtelne i szkodliwe zapytania.

Oto przykład sposobu wykonywania parametrów z serwerem Sql:

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

Procedurom składowanym czasami przypisuje się zapobieganie wstrzykiwaniu kodu SQL. Jednak w większości przypadków nadal musisz wywoływać je za pomocą parametrów zapytania lub nie pomagają. Jeśli używasz wyłącznie procedur składowanych , możesz wyłączyć uprawnienia SELECT, UPDATE, ALTER, CREATE, DELETE itp. (Prawie wszystko oprócz EXEC) dla konta użytkownika aplikacji i uzyskać w ten sposób pewną ochronę.


Czy możesz to dalej wyjaśnić cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz?
Cary Bondoc

1
@CaryBondoc, co chcesz wiedzieć? Ten wiersz tworzy parametr o nazwie, @Bazktóry jest typu varchar(50), któremu przypisano wartość Bazciągu.
JB King

możesz też powiedzieć „command.parameters.addiwthvalue („ @ Baz ”, 50)”
Gavin Perkins

2
@GavinPerkins Zakładając, że miałeś na myśli AddWithValue("@Baz", Baz), możesz to zrobić, ale nie powinieneś , zwłaszcza że konwersja wartości ciągów, które są domyślnie mapowane nvarcharna rzeczywisty varchartyp, jest jednym z najczęstszych miejsc, które mogą wywoływać efekty wymienione w tym łączu.
Joel Coehoorn

15

Zdecydowanie ostatni, tj

A może muszę zrobić coś bardziej rozbudowanego ...? (Tak, cmd.Parameters.Add())

Zapytania parametryzowane mają dwie główne zalety:

  • Bezpieczeństwo: to dobry sposób na uniknięcie luk typu SQL Injection
  • Wydajność: Jeśli regularnie wywołujesz to samo zapytanie tylko z różnymi parametrami, sparametryzowane zapytanie może pozwolić bazie danych na buforowanie zapytań, co jest znacznym źródłem wzrostu wydajności.
  • Dodatkowo: nie musisz martwić się o problemy z formatowaniem daty i godziny w kodzie bazy danych. Podobnie, jeśli Twój kod będzie kiedykolwiek działał na maszynach z ustawieniami narodowymi innymi niż angielski, nie będziesz mieć problemów z przecinkami dziesiętnymi.

5

Chcesz przejść do ostatniego przykładu, ponieważ jest to jedyny prawdziwie sparametryzowany. Oprócz obaw związanych z bezpieczeństwem (które są znacznie bardziej rozpowszechnione niż mogłoby się wydawać) najlepiej pozwolić ADO.NET zająć się parametryzacją, ponieważ nie możesz być pewien, czy wartość, którą przekazujesz, wymaga pojedynczych cudzysłowów, czy nie bez sprawdzenia Typekażdego parametru .

[Edytuj] Oto przykład:

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
Uważaj na to: .Net to Unicode, więc parametr domyślnie przyjmie NVarChar. Jeśli jest to naprawdę kolumna VarChar, może to spowodować duże problemy z wydajnością.
Joel Coehoorn

2

Większość ludzi zrobiłaby to za pomocą biblioteki języka programowania po stronie serwera, takiej jak PDO PHP lub Perl DBI.

Na przykład w przypadku ChNP:

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

Dba to o ucieczkę danych do wstawienia do bazy danych.

Zaletą jest to, że możesz wielokrotnie powtarzać wstawkę z jednym przygotowanym stwierdzeniem, uzyskując przewagę szybkości.

Na przykład w powyższym zapytaniu mogłem raz przygotować instrukcję, a następnie utworzyć pętlę nad tworzeniem tablicy danych z zestawu danych i powtórzyć -> wykonać tyle razy, ile potrzeba.


1

Twój tekst polecenia musi wyglądać następująco:

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

cmdText = "EXEC foo_from_baz ?"

Następnie dodaj wartości parametrów. W ten sposób zapewnia, że ​​wartość zostanie użyta tylko jako wartość, podczas gdy w przypadku drugiej metody, jeśli zmienna fuz jest ustawiona na

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

czy widzisz, co może się stać?


0

Oto krótka klasa, na której można zacząć od języka SQL. Możesz z niej budować i dodawać do klasy.

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.