Dlaczego zawsze wolimy używać parametrów w instrukcjach SQL?

114

Jestem bardzo nowy w pracy z bazami danych. Teraz mogę napisać SELECT, UPDATE, DELETEi INSERTpoleceń. Ale widziałem wiele forów, na których wolimy pisać:

SELECT empSalary from employee where salary = @salary

...zamiast:

SELECT empSalary from employee where salary = txtSalary.Text

Dlaczego zawsze wolimy używać parametrów i jak ich używać?

Chciałem poznać zastosowanie i zalety pierwszej metody. Słyszałem nawet o iniekcji SQL, ale nie do końca to rozumiem. Nie wiem nawet, czy wstrzyknięcie SQL jest związane z moim pytaniem.

Piaszczysty
źródło
2
Masz rację, jest to związane z iniekcją SQL. To, jak radzić sobie z parametrami, jest zwykle obowiązkiem każdego języka / frameworka, w którym działa twój program, i może to zależeć od języka. Opublikuj zarówno swoje RDBMS (pomocne), jak i ramy ORM (konieczne).
Clockwork-Muse
1
Używam C # jako języka programowania i SQL Server 2008 jako bazy danych. Używam Microsoft dotNet Framework 4.0. Naprawdę bardzo mi przykro, że nie jestem pewien, o co pytasz (RDBMS lub ORM), być może możesz teraz dać mi moje wersje RDBMS i ORM framework :-). Wielkie dzięki
Sandy
1
RDBMS to Twoja baza danych, w twoim przypadku SQL Server 2008. Twój ORM to metoda, za pomocą której uzyskujesz dostęp do bazy danych, w tym przypadku ADO.NET. Inne obejmują LINQ to SQL i Entity Framework . W rzeczywistości, kiedy już nauczysz się podstaw ADO.NET i SQL, polecam użycie ORM, takiego jak LINQ lub EF, ponieważ rozwiązują one wiele problemów, które możesz napotkać, ręcznie pisząc SQL.
Chad Levy,

Odpowiedzi:

129

Używanie parametrów pomaga zapobiegać atakom typu SQL Injection gdy baza danych jest używana w połączeniu z interfejsem programu, takim jak program komputerowy lub witryna internetowa.

W Twoim przykładzie użytkownik może bezpośrednio uruchomić kod SQL w bazie danych, tworząc instrukcje w programie txtSalary.

Na przykład, gdyby mieli pisać 0 OR 1=1, wykonywany SQL byłby

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

przy czym wszystkie empSalary zostaną zwrócone.

Ponadto użytkownik może wykonać znacznie gorsze polecenia względem bazy danych, w tym usunąć ją, jeśli napisał 0; Drop Table employee:

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

Tabela employeezostanie wówczas usunięta.


W Twoim przypadku wygląda na to, że używasz platformy .NET. Korzystanie z parametrów jest tak proste, jak:

DO#

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

Edycja 2016-4-25:

Zgodnie z komentarzem George'a Stockera, zmieniłem przykładowy kod na nieużywany AddWithValue. Generalnie zaleca się również zawijanie instrukcji IDisposablew usinginstrukcje.

Chad Levy
źródło
świetne rozwiązanie. Ale czy możesz wyjaśnić nieco więcej, dlaczego i jak używanie parametrów jest bezpieczne. Chodzi mi o to, że nadal wygląda na to, że polecenie sql będzie takie samo
Sandy
czy możemy dodać wiele parametrów do polecenia sql. Jak możemy wymagać w poleceniu INSERT?
Sandy
2
SQL Server traktuje tekst wewnątrz parametrów tylko jako dane wejściowe i nigdy go nie wykona.
Chad Levy,
3
Tak, można dodać wiele parametrów: Insert Into table (Col1, Col2) Values (@Col1, @Col2). W swoim kodzie dodałbyś wiele AddWithValues.
Chad Levy,
1
Nie używaj AddWithValue! Może to powodować niejawne problemy z konwersją. Zawsze ustawiaj rozmiar jawnie i dodaj wartość parametru za pomocą parameter.Value = someValue.
George Stocker
75

Masz rację, jest to związane z iniekcją SQL , która jest luką umożliwiającą malicioius użytkownikowi wykonanie dowolnych instrukcji w Twojej bazie danych. Ten ulubiony komiks XKCD z dawnych czasów ilustruje koncepcję:

Jej córka ma na imię Pomóż mi, jestem uwięziona w fabryce praw jazdy.


W twoim przykładzie, jeśli użyjesz tylko:

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

Jesteś otwarty na iniekcje SQL. Na przykład załóżmy, że ktoś wchodzi do txtSalary:

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

Po wykonaniu tego zapytania wykona on SELECT i UPDATElub DROP, lub cokolwiek chcieli. Na --końcu po prostu komentuje resztę twojego zapytania, co byłoby przydatne w ataku, gdybyś później cokolwiek konkatenował txtSalary.Text.


Poprawnym sposobem jest użycie sparametryzowanych zapytań, np. (C #):

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

Dzięki temu możesz bezpiecznie wykonać zapytanie.

Aby dowiedzieć się, jak uniknąć wstrzykiwania SQL w kilku innych językach, odwiedź bobby-tables.com , witrynę utrzymywaną przez użytkownika SO .

NullUserException
źródło
1
świetne rozwiązanie. Ale czy możesz wyjaśnić nieco więcej, dlaczego i jak używanie parametrów jest bezpieczne. Chodzi mi o to, że nadal wygląda na to, że polecenie sql będzie takie samo.
Sandy
1
@ user815600: powszechne nieporozumienie - nadal wierzysz, że zapytanie z parametrami przyjmie wartość i zastąpi parametry rzeczywistymi wartościami - prawda? Nie, to się nie dzieje! - zamiast tego instrukcja SQL z parametrami zostanie przesłana do SQL Server wraz z listą parametrów i ich wartościami - instrukcja SQL nie będzie taka sama
marc_s
1
oznacza to, że wstrzyknięcie sql jest monitorowane przez wewnętrzny mechanizm lub zabezpieczenia serwera sql. dzięki.
Sandy
4
Tak jak lubię kreskówki, jeśli uruchamiasz swój kod z wystarczającymi uprawnieniami do usuwania tabel, prawdopodobnie masz szersze problemy.
philw,
9

Oprócz innych odpowiedzi należy dodać, że parametry nie tylko pomagają zapobiegać wstrzykiwaniu sql, ale mogą poprawiać wydajność zapytań . Sql Server buforuje sparametryzowane plany zapytań i ponownie wykorzystuj je podczas wykonywania powtarzających się zapytań. Jeśli nie sparametryzowałeś zapytania, serwer sql kompilowałby nowy plan przy każdym wykonaniu zapytania (z pewnymi wyjątkami), jeśli tekst zapytania byłby inny.

Więcej informacji na temat buforowania planu zapytania

Oleg
źródło
1
Jest to bardziej istotne, niż mogłoby się wydawać. Nawet „małe” zapytanie można wykonać tysiące lub miliony razy, skutecznie opróżniając całą pamięć podręczną zapytań.
James
5

Dwa lata po moim pierwszym wyjeździe recydywa ...

Dlaczego preferujemy parametry? Wstrzyknięcie SQL to oczywiście duży powód, ale może być tak, że potajemnie pragniemy wrócić do SQL jako języka . SQL w literałach łańcuchowych jest już dziwną praktyką kulturową, ale przynajmniej możesz skopiować i wkleić swoje żądanie do studia zarządzania. SQL skonstruowany dynamicznie z warunkami warunkowymi i strukturami kontrolnymi języka hosta, gdy SQL ma warunki warunkowe i struktury kontrolne, jest po prostu barbarzyństwem poziomu 0. Musisz uruchomić aplikację w trybie debugowania lub ze śladem, aby zobaczyć, jaki SQL generuje.

Nie poprzestawaj tylko na parametrach. Idź na całość i użyj QueryFirst (zastrzeżenie: które napisałem). Twój SQL żyje w pliku .sql. Edytujesz go w fantastycznym oknie edytora TSQL, z walidacją składni i Intellisense dla tabel i kolumn. Możesz przypisać dane testowe w specjalnej sekcji komentarzy i kliknąć „odtwórz”, aby uruchomić zapytanie bezpośrednio w oknie. Tworzenie parametru jest tak proste, jak umieszczenie „@myParam” w kodzie SQL. Następnie za każdym razem, gdy zapisujesz, QueryFirst generuje otokę C # dla zapytania. Twoje parametry pojawiają się, silnie wpisane, jako argumenty metod Execute (). Twoje wyniki są zwracane w IEnumerable lub liście POCO o jednoznacznie określonym typie, typach wygenerowanych na podstawie rzeczywistego schematu zwróconego przez zapytanie. Jeśli zapytanie nie zostanie uruchomione, aplikacja nie zostanie skompilowana. Jeśli schemat bazy danych ulegnie zmianie, a zapytanie zostanie uruchomione, ale niektóre kolumny znikną, błąd kompilacji wskazuje na na wiersz w kodziektóry próbuje uzyskać dostęp do brakujących danych. Jest też wiele innych zalet. Dlaczego chcesz uzyskać dostęp do danych w inny sposób?

bbsimonbb
źródło
4

W Sql, gdy dowolne słowo zawiera znak @, oznacza to, że jest zmienne i używamy tej zmiennej do ustawiania w niej wartości i używamy jej w obszarze liczbowym w tym samym skrypcie sql, ponieważ jest ograniczone tylko do pojedynczego skryptu, podczas gdy możesz zadeklarować wiele zmiennych tego samego typu i nazwy w wielu skryptach. Używamy tej zmiennej w partii procedur składowanych, ponieważ procedury składowane są wstępnie skompilowanymi zapytaniami i możemy przekazywać wartości w tych zmiennych ze skryptu, pulpitu i stron internetowych, aby uzyskać dalsze informacje, przeczytaj artykuł Deklaracja zmiennej lokalnej , procedura składowana SQL i wstrzyknięcia sql .

Przeczytaj również artykuł Ochrona przed wstrzyknięciem sql, który zawiera wskazówki, jak chronić bazę danych.

Mam nadzieję, że pomoże ci to zrozumieć również każde pytanie, które komentuję.

Emaad Ali
źródło
3

Inne odpowiedzi wyjaśniają, dlaczego parametry są ważne, ale ma też wadę! W .net istnieje kilka metod tworzenia parametrów (Add, AddWithValue), ale wszystkie one wymagają niepotrzebnej troski o nazwę parametru i wszystkie zmniejszają czytelność kodu SQL w kodzie. Kiedy próbujesz medytować nad kodem SQL, musisz przeszukać okolice powyżej lub poniżej, aby zobaczyć, jaka wartość została użyta w parametrze.

Pokornie twierdzę, że moja mała klasa SqlBuilder jest najbardziej eleganckim sposobem pisania zapytań parametrycznych . Twój kod będzie wyglądał następująco ...

DO#

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

Twój kod będzie krótszy i znacznie bardziej czytelny. Nie potrzebujesz nawet dodatkowych wierszy, a kiedy czytasz, nie musisz szukać wartości parametrów. Klasa, której potrzebujesz, jest tutaj ...

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
źródło
Nazwanie parametrów z pewnością pomaga podczas profilowania zapytań, które uruchamia serwer.
Dave R.
Mój szef powiedział to samo. Jeśli znaczące nazwy parametrów są dla Ciebie ważne, dodaj argument paramName do metody wartości. Podejrzewam, że niepotrzebnie komplikujesz sprawy.
bbsimonbb
Kiepski pomysł. Jak powiedziano wcześniej, AddWithValuemoże powodować niejawne problemy z konwersją.
Adam Calvet Bohl
@Adam masz rację, ale to nie powstrzymuje AddWithValue () przed bardzo szerokim użyciem i nie sądzę, aby unieważniało to pomysł. Ale w międzyczasie wymyśliłem znacznie lepszy sposób pisania zapytań parametrycznych, który nie używa AddWithValue () :-)
bbsimonbb
Dobrze! Obiecuję, że niedługo się temu przyjrzę!
Adam Calvet Bohl
3

Stary post, ale chciałem upewnić się, że nowicjusze są świadomi procedur przechowywanych .

Moja warta 10 centów jest taka, że ​​jeśli jesteś w stanie napisać instrukcję SQL jako procedurę składowaną , moim zdaniem jest to optymalne podejście. I ZAWSZE używać przechowywanych procs i nigdy Przelotowe zapisów w moim głównym kodu. Na przykład SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Korzystając z procedur składowanych, można ograniczyć użytkownika tylko do uprawnień EXECUTE , zmniejszając w ten sposób zagrożenia bezpieczeństwa .

Twoja procedura składowana jest z natury parameryzowana i można określić parametry wejściowe i wyjściowe.

Procedura składowana (jeśli zwraca dane za pośrednictwem SELECTinstrukcji) jest dostępna i odczytywana w taki sam sposób, jak zwykła SELECTinstrukcja w kodzie.

Działa również szybciej, ponieważ jest kompilowany na serwerze SQL.

Czy wspomniałem też, że możesz wykonać wiele kroków, np. updateTabelę, sprawdzić wartości na innym serwerze DB, a po zakończeniu, zwrócić dane do klienta, wszystko na tym samym serwerze i bez interakcji z klientem. Jest to więc DUŻO szybsze niż zakodowanie tej logiki w kodzie.

Arvin Amir
źródło