Dlaczego mechanizm zapobiegania iniekcji SQL ewoluował w kierunku używania sparametryzowanych zapytań?

59

Z mojego punktu widzenia atakom wstrzykiwania SQL można zapobiec poprzez:

  1. Dokładne przeglądanie, filtrowanie, kodowanie danych wejściowych (przed wstawieniem do SQL)
  2. Korzystanie z przygotowanych instrukcji / sparametryzowanych zapytań

Przypuszczam, że każdy ma swoje zalety i wady, ale dlaczego numer 2 wystartował i został uznany za mniej więcej faktyczny sposób zapobiegania atakom iniekcyjnym? Czy jest to po prostu bezpieczniejsze i mniej podatne na błędy, czy też były inne czynniki?

Rozumiem, że jeśli numer 1 jest używany właściwie i wszystkie zastrzeżenia są załatwione, może być tak samo skuteczny jak numer 2.

Odkażanie, filtrowanie i kodowanie

Z mojej strony było pewne zamieszanie między znaczeniem dezynfekcji , filtrowania i kodowania . Powiem, że dla moich celów wszystkie powyższe można rozważyć dla opcji 1. W tym przypadku rozumiem, że odkażanie i filtrowanie może modyfikować lub odrzucać dane wejściowe, podczas gdy kodowanie zachowuje dane takie, jakie jest , ale koduje je odpowiednio, aby uniknąć ataków iniekcyjnych. Uważam, że ucieczkę danych można uznać za sposób ich zakodowania.

Zapytania sparametryzowane a biblioteka kodowania

Istnieją odpowiedzi, w których pojęcia parameterized queriesi encoding librariesktóre są traktowane zamiennie. Popraw mnie, jeśli się mylę, ale mam wrażenie, że się różnią.

Rozumiem, że encoding librariesbez względu na to, jak dobrzy są zawsze, mogą modyfikować „Program” SQL, ponieważ wprowadzają zmiany w samym SQL, zanim zostanie on wysłany do RDBMS.

Parameterized queries z drugiej strony wyślij program SQL do RDBMS, który następnie zoptymalizuje zapytanie, zdefiniuje plan wykonania zapytania, wybierze indeksy, które mają zostać użyte itp., a następnie włączy dane, jako ostatni krok w RDBMS samo.

Biblioteka kodowania

  data -> (encoding library)
                  |
                  v
SQL -> (SQL + encoded data) -> RDBMS (execution plan defined) -> execute statement

Zapytanie sparametryzowane

                                               data
                                                 |
                                                 v
SQL -> RDBMS (query execution plan defined) -> data -> execute statement

Znaczenie historyczne

Niektóre odpowiedzi wspominają, że historycznie sparametryzowane zapytania były tworzone ze względu na wydajność, a przed atakami iniekcyjnymi ukierunkowanymi na problemy z kodowaniem stały się popularne. W pewnym momencie stało się jasne, że PQ były również dość skuteczne przeciwko atakom iniekcyjnym. Aby trzymać się ducha mojego pytania, dlaczego PQ pozostało metodą z wyboru i dlaczego rozkwitło ponad większość innych metod, jeśli chodzi o zapobieganie atakom typu SQL injection?

Dennis
źródło
1
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
wałek klonowy
23
Przygotowane instrukcje nie są wynikiem ewolucji po atakach SQL injection. Byli tam od początku. Twoje pytanie opiera się na fałszywej przesłance.
user207421,
4
Jeśli uważasz, że jesteś mądrzejszy od złych facetów, wybierz numer 1
paparazzo
1
„dlaczego PQ pozostało metodą wyboru” Ponieważ jest to najłatwiejszy i najbardziej niezawodny. Plus wyżej wymienione zalety wydajności do PQ. Naprawdę nie ma wady.
Paul Draper,
1
Ponieważ jest to prawidłowe rozwiązanie problemu wykonywania zapytań, nawet jeśli nie dotyczyłoby to iniekcji SQL w kontekście bezpieczeństwa . Formularze wymagające ucieczki i używania danych wewnątrz pasma z poleceniami są zawsze błędem projektowym, ponieważ są podatne na błędy, sprzeczne z intuicją i źle się psują, gdy są używane nieprawidłowo. Zobacz także: skrypty powłoki.
R ..

Odpowiedzi:

147

Problem polega na tym, że # 1 wymaga skutecznego parsowania i interpretacji całego wariantu SQL, z którym pracujesz, abyś wiedział, czy robi coś, czego nie powinien. I aktualizuj ten kod podczas aktualizacji bazy danych. Wszędzie akceptujesz dane wejściowe dla swoich zapytań. I nie zepsuj tego.

Tak, tak, to powstrzymałoby ataki typu SQL injection, ale jego wdrożenie jest absurdalnie bardziej kosztowne.

Telastyn
źródło
60
@dennis - Cóż, jaki jest cytat w twoim wariancie SQL? „? '?”? U + 2018? \ U2018? Czy istnieją sztuczki, aby rozdzielić wyrażenia? Czy twoje podzapytania mogą aktualizować? Jest wiele rzeczy do rozważenia.
Telastyn
7
@Dennis każdy silnik DB ma swój własny sposób robienia takich rzeczy, jak unikanie znaków w ciągach znaków. Jest to wiele dziur do zatkania, szczególnie jeśli aplikacja musi współpracować z wieloma silnikami DB lub być kompatybilna z przyszłymi wersjami tego samego silnika, które mogą zmienić niewielką składnię zapytań, którą można wykorzystać.
12
Kolejną zaletą przygotowanych instrukcji jest wzrost wydajności, który trzeba uzyskać, gdy trzeba ponownie uruchomić to samo zapytanie z różnymi wartościami. Ponadto przygotowane instrukcje mogą wiedzieć, czy wartość jest naprawdę rozumiana jako nullciąg, czy liczba i działają odpowiednio. Jest to bardzo dobre dla bezpieczeństwa. I nawet jeśli uruchomisz zapytanie raz, silnik DB już go zoptymalizuje. Jeszcze lepiej, jeśli jest buforowany!
Ismael Miguel
8
@Dennis Pan Henry Null podziękuje za zrobienie tego we właściwy sposób.
Mathieu Guindon,
14
@Dennis imię nie ma znaczenia. Problem dotyczy nazwiska. Zobacz Przepełnienie stosu , Programmers.SE , Fox Sports , Wired , BBC i cokolwiek innego, co możesz znaleźć w szybkiej wyszukiwarce Google ;-)
Mathieu Guindon
80

Ponieważ opcja 1 nie jest rozwiązaniem. Filtrowanie i filtrowanie oznacza odrzucanie lub usuwanie nieprawidłowych danych wejściowych. Ale każde dane wejściowe mogą być prawidłowe. Na przykład apostrof jest prawidłową postacią w nazwie „O'Malley”. Po prostu musi być poprawnie zakodowany przed użyciem w SQL, co właśnie robią przygotowane instrukcje.


Po dodaniu notatki wydaje się, że w zasadzie pytasz, dlaczego warto korzystać ze standardowej funkcji bibliotecznej zamiast pisać od podstaw własny funkcjonalnie podobny kod? Należy zawsze wolą standardowe rozwiązania biblioteczne do pisania własnego kodu. Jest to mniej pracy i łatwiejsze w utrzymaniu. Dzieje się tak w przypadku każdej funkcjonalności, ale szczególnie w przypadku czegoś, co jest wrażliwe na bezpieczeństwo, absolutnie nie ma sensu wymyślać koła samodzielnie.

JacquesB
źródło
2
To jest to (i to była brakująca część w dwóch innych odpowiedziach, więc +1). Biorąc pod uwagę sposób sformułowania pytania, nie chodzi o dezynfekcję danych wejściowych użytkownika, ale cytuję pytanie: „filtrowanie danych wejściowych (przed wstawieniem)”. Jeśli pytanie dotyczy teraz dezynfekcji danych wejściowych, to dlaczego miałbyś to zrobić sam, zamiast pozwolić bibliotece to zrobić (a przy okazji utracić możliwość buforowania planów wykonania)?
Arseni Mourzenko
8
@Dennis: Odkażanie lub filtrowanie oznacza usuwanie informacji. Kodowanie oznacza przekształcenie reprezentacji danych bez utraty informacji.
JacquesB
9
@Dennis: filtrowanie oznacza akceptację lub odrzucenie danych wejściowych użytkownika. Na przykład „Jeff” będzie filtrowane jako dane wejściowe w polu „Wiek użytkownika”, ponieważ wartość jest oczywiście nieprawidłowa. Jeśli zamiast filtrować dane wejściowe, zaczniesz je przekształcać, na przykład zastępując znak pojedynczego cudzysłowu, to robisz dokładnie to samo, co biblioteki baz danych, w których używają sparametryzowanych zapytań; w tym przypadku twoje pytanie brzmi: „Dlaczego miałbym używać czegoś, co istnieje i zostało napisane przez ekspertów w tej dziedzinie, skoro mogę
wymyślić
3
@Dennis: O\'Malleyużywa ukośnika, aby uciec od cytatu w celu poprawnego wstawienia (przynajmniej w niektórych bazach danych). W MS SQL lub Access można uciec z dodatkowym cytatem O''Malley. Niezbyt przenośny, jeśli musisz to zrobić sam.
AbraCadaver
5
Nie mogę powiedzieć, ile razy moje imię zostało całkowicie odrzucone przez system. Czasami nawet widziałem błędy spowodowane przez wstrzyknięcie SQL po prostu z używania mojego nazwiska. Cholera, kiedyś zostałem poproszony o zmianę nazwy użytkownika, ponieważ w rzeczywistości coś popsułem na backendie.
Alexander O'Mara,
60

Jeśli próbujesz wykonać ciąg znaków, tak naprawdę nie generujesz zapytania SQL. Generujesz ciąg, który może wygenerować zapytanie SQL. Istnieje poziom pośredni, który otwiera dużo miejsca na błędy i błędy. To naprawdę trochę zaskakujące, biorąc pod uwagę, że w większości kontekstów chętnie wchodzimy w interakcje programowe. Na przykład, jeśli mamy jakąś strukturę listy i chcemy dodać element, zwykle nie robimy:

List<Integer> list = /* a list of 1, 2, 3 */
String strList = list.toString();   /* to get "[1, 2, 3]" */
strList = /* manipulate strList to become "[1, 2, 5, 3]" */
list = parseList(strList);

Jeśli ktoś sugeruje zrobienie tego, słusznie odpowiedziałbyś, że jest to raczej niedorzeczne i że należy po prostu:

List<Integer> list = /* ... */;
list.add(5, position=2);

Wpływa to na strukturę danych na poziomie koncepcyjnym. Nie wprowadza żadnej zależności od sposobu drukowania lub analizowania tej struktury. To są całkowicie ortogonalne decyzje.

Twoje pierwsze podejście jest jak pierwsza próbka (tylko trochę gorzej): zakładasz, że możesz programowo skonstruować ciąg, który zostanie poprawnie przeanalizowany jako pożądane zapytanie. To zależy od parsera i całej logiki przetwarzania łańcucha.

Drugie podejście polegające na użyciu przygotowanych zapytań jest bardziej podobne do drugiej próbki. Kiedy używasz przygotowanego zapytania, zasadniczo analizujesz pseudo-zapytanie, które jest zgodne z prawem, ale ma w nim pewne symbole zastępcze, a następnie używasz interfejsu API, aby poprawnie zastąpić niektóre wartości. Nie musisz już angażować procesu analizowania i nie musisz się martwić przetwarzaniem ciągów.

Zasadniczo interakcja z rzeczami na poziomie koncepcyjnym jest znacznie łatwiejsza i znacznie mniej podatna na błędy. Zapytanie nie jest ciągiem, zapytanie jest tym, co otrzymujesz, gdy analizujesz ciąg lub konstruujesz go programowo (lub jakakolwiek inna metoda pozwala ci go utworzyć).

Istnieje dobra analogia między makrami w stylu C, które wykonują prostą zamianę tekstu, a makrami w stylu Lisp, które generują dowolne kody. Za pomocą makr w stylu C możesz zamieniać tekst w kodzie źródłowym, co oznacza, że ​​możesz wprowadzać błędy składniowe lub wprowadzające w błąd zachowania. Za pomocą makr Lisp generujesz kod w postaci, w której kompilator go przetwarza (to znaczy zwracasz faktyczne struktury danych przetwarzane przez kompilator, a nie tekst, który czytnik musi przetworzyć, zanim kompilator będzie mógł do niego dotrzeć) . Jednak za pomocą makra Lisp nie można wygenerować czegoś, co byłoby błędem analizy. Np. Nie można wygenerować (let (()) a .

Nawet z makrami Lisp nadal możesz generować zły kod, ponieważ niekoniecznie musisz być świadomy struktury, która powinna tam być. Np. W Lisp, (niech (ab)) a) oznacza „ustanowienie nowego leksykalnego wiązania zmiennej a z wartością zmiennej b, a następnie zwrócenie wartości a”, a (niech (ab) a) oznacza „ustal nowe powiązania leksykalne zmiennych a i b i zainicjuj je oba do zera, a następnie zwróć wartość a.” Oba są poprawne pod względem składniowym, ale oznaczają różne rzeczy. Aby uniknąć tego problemu, możesz użyć bardziej świadomych semantycznie funkcji i zrobić coś takiego:

Variable a = new Variable("a");
Variable b = new Variable("b");
Let let = new Let();
let.getBindings().add(new LetBinding(a,b));
let.setBody(a);
return let;

Przy czymś takim nie można zwrócić czegoś, co jest składniowo nieprawidłowe, i znacznie trudniej jest zwrócić coś, co przypadkowo nie jest tym, czego chciałeś.

Joshua Taylor
źródło
Dobre wytłumaczenie!
Mike Partridge
2
Straciłeś mnie na „dobrej analogii”, ale głosowałem na podstawie poprzedniego wyjaśnienia. :)
Wildcard
1
Doskonały przykład! - I możesz dodać: W zależności od typu danych czasami nie jest nawet możliwe ani wykonalne utworzenie ciągu analizowalnego. - Co jeśli jednym z moich parametrów jest pole tekstowe zawierające szkic artykułu (~ 10.000 znaków)? a co jeśli jednym z parametrów jest obraz JPG ? - Jedynym sposobem jest zatem sparametryzowane zapytanie
Falco
Właściwie nie - jest to dość zły opis tego, dlaczego przygotowane oświadczenia ewoluowały jako obrona przed zastrzykiem sql. Szczególnie podany przykład kodu znajduje się w języku Java, którego nie było w pobliżu, gdy sparametryzowane zapytania były opracowywane prawdopodobnie w czasie, w którym C / C ++ uznano za najnowocześniejszy. Bazy danych SQL zaczęły być używane we wczesnych latach 1970–1980. SPOSÓB, aby popularne były języki wyższe. Cholera, powiedziałbym, że wielu z nich ułatwiło pracę z bazami danych (ktoś PowerBuilder?)
TomTom
@TomTom, zgadzam się z większością twoich treści. Dotknąłem tutaj jedynie kwestii bezpieczeństwa. Na SO odpowiadam na wiele pytań SPARQL (język zapytań RDF, z pewnymi podobieństwami do SQL) i wiele osób ma problemy, ponieważ łączą one łańcuchy zamiast sparametryzowanych zapytań. Nawet bez ataków z użyciem iniekcji sparametryzowane zapytania pomagają uniknąć błędów / awarii, a błędy / awarie mogą również stanowić problemy z bezpieczeństwem, nawet jeśli nie są atakami z zastrzyków. Powiedziałbym więc coraz mniej: sparametryzowane zapytania są dobre, nawet jeśli wstrzyknięcie SQL nie stanowiło problemu, i są dobre ...
Joshua Taylor
21

Pomaga to, że opcja nr 2 jest ogólnie uważana za najlepszą praktykę, ponieważ baza danych może buforować nieparametryzowaną wersję zapytania. Zapytania sparametryzowane wyprzedzają problem wstrzykiwania SQL o kilka lat (tak mi się wydaje), tak się składa, że ​​możesz zabić dwa ptaki jednym kamieniem.

Jason B.
źródło
10
Wstrzykiwanie SQL jest problemem od momentu wynalezienia SQL. Później nie stało się to problemem.
Servy
9
@Servy Teoretycznie tak. Praktycznie stało się to prawdziwym problemem, gdy nasze mechanizmy wejściowe przełączyły się w tryb online, oferując potężną powierzchnię do ataku dla każdego, kto mógłby uderzyć.
Jan Doggen,
8
Little Bobby Tables nie zgodziłby się z tym, że potrzebujesz internetu lub dużej bazy użytkowników, aby wykorzystać zastrzyk SQL. I oczywiście sieci pre-date SQL, więc nie jest tak, że trzeba będzie czekać na sieci, kiedy SQL pojawi się. Tak, luk w zabezpieczeniach są mniej podatne gdy aplikacja ma małą bazę użytkowników, ale są jeszcze luki w zabezpieczeniach, a ludzie nie wykorzystują ich, gdy sama baza danych zawiera cenne dane (i wielu bardzo wcześnie w bazie miał bardzo cenne dane, jak tylko ludzie z cennymi bazami danych może sobie pozwolić na technologię).
Servy
5
@ Według mojej wiedzy, dynamiczny SQL był stosunkowo późną funkcją; początkowe użycie SQL było głównie wstępnie skompilowane / wstępnie przetworzone z parametrami wartości (zarówno wejściowymi, jak i wyjściowymi), więc parametry w zapytaniach mogą poprzedzać wstrzykiwanie SQL w oprogramowaniu (być może nie w zapytaniach ad-hoc / CLI).
Mark Rotteveel,
6
Mogą wyprzedzać świadomość wstrzykiwania SQL.
user253751
20

Mówiąc wprost: nie zrobili tego. Twoje oświadczenie:

Dlaczego mechanizm zapobiegania iniekcji SQL ewoluował w kierunku używania zapytań sparametryzowanych?

jest zasadniczo wadliwy. Zapytania sparametryzowane istniały znacznie dłużej niż SQL Injection jest przynajmniej powszechnie znany. Zostały one ogólnie opracowane jako sposób na uniknięcie koncentracji ciągów znaków w zwykłej funkcjonalności „formy wyszukiwania”, jaką mają aplikacje LOB (Line of Business). Wiele - WIELE lat później, ktoś znalazł problem z bezpieczeństwem podczas manipulacji ciągiem.

Pamiętam, jak robiłem SQL 25 lat temu (kiedy Internet NIE był szeroko używany - dopiero się zaczynał) i pamiętam, że robiłem SQL vs. IBM DB5 IIRC wersja 5 - i to już sparametryzowało zapytania.

TomTom
źródło
dzięki. Dlaczego trzeba unikać konkatenacji łańcuchów? Wydaje mi się, że byłaby to przydatna funkcja. Czy ktoś miał z tym problem?
Dennis
3
Dwa właściwie. Po pierwsze, nie zawsze jest to całkowicie trywialne - po co zajmować się alokacją pamięci itp., Gdy nie jest to potrzebne. Ale po drugie, w czasach starożytnych buforowanie wydajności po stronie bazy danych SQL nie było tak świetne - kompilacja SQL była droga. Jako efekt uboczny użycia jednej przygotowanej instrukcji SQL (z której pochodzą parametry) można ponownie wykorzystać plany egzekucji. SQL Server wprowadził automatyczną parametryzację (aby ponownie wykorzystywać plany zapytań nawet bez parametrów - są one odejmowane i sugerowane) Myślę, że albo 2000 albo 2007 - gdzieś pomiędzy, IIRC.
TomTom
2
Posiadanie sparametryzowanych zapytań nie eliminuje możliwości łączenia łańcuchów. Można wykonać konkatenację łańcuchów, aby wygenerować sparametryzowane zapytanie. To, że funkcja jest przydatna, nie oznacza, że ​​zawsze jest dobrym wyborem dla danego problemu.
JimmyJames
Tak, ale jak powiedziałem - do czasu ich wynalezienia dynamiczny SQL przyszedł z całkiem przyzwoitym spadkiem wydajności;) Nawet dzisiaj ludzie mówią ci, że dynamiczne plany zapytań SQL na serwerze sql nie są ponownie wykorzystywane (co jest złe, ponieważ - hm - as Powiedziałem, że jakiś punkt między 2000 a 2007 rokiem - więc Dość długi). W tamtym czasie naprawdę chciałeś instrukcji PREPARED, jeśli uruchamiasz sql wiele razy;)
TomTom
Planowanie buforowania dla dynamicznego SQL zostało w rzeczywistości dodane do SQL Server 7.0, w 1998 r. - sqlmag.com/database-performance-tuning/…
Mike Dimmick
13

Oprócz wszystkich innych dobrych odpowiedzi:

Powodem, dla którego numer 2 jest lepszy, jest to, że oddziela dane od kodu. W nr 1 twoje dane są częścią twojego kodu i stąd pochodzą wszystkie złe rzeczy. Z numerem 1 otrzymujesz zapytanie i musisz wykonać dodatkowe kroki, aby upewnić się, że zapytanie rozpoznaje twoje dane jako dane, podczas gdy z nr 2 otrzymujesz kod i jego kod, a dane to dane.

Pieter B.
źródło
3
Oddzielenie kodu i danych oznacza również, że twoja ochrona przed wstrzykiwaniem wrogiego kodu została napisana i przetestowana przez dostawcę bazy danych. Dlatego jeśli coś przekazane jako parametr wraz z nieszkodliwym zapytaniem skończy się niszczeniem lub podważeniem bazy danych, reputacja firmy bazodanowej jest na linii, a Twoja organizacja może nawet pozwać je i wygrać. Oznacza to również, że jeśli ten kod zawiera błąd, który można wykorzystać, szanse są całkiem duże, że jest to czyjaś strona, na której wszystko się psuje, a nie twoja. (Tylko nie ignoruj ​​poprawek bezpieczeństwa!)
nigel222 15.09.16
11

Zapytania sparametryzowane, oprócz zapewnienia ochrony przed wstrzyknięciem SQL, często mają dodatkową zaletę, że są kompilowane tylko raz, a następnie wykonywane wielokrotnie z różnymi parametrami.

Z punktu widzenia bazy danych SQL select * from employees where last_name = 'Smith'i select * from employees where last_name = 'Fisher'są wyraźnie różne i dlatego wymagają oddzielnego parsowania, kompilacji i optymalizacji. Będą również zajmować osobne miejsca w obszarze pamięci przeznaczone do przechowywania skompilowanych instrukcji. W mocno obciążonym systemie z dużą liczbą podobnych zapytań, które mają różne parametry obliczeń i obciążenie pamięci może być znaczne.

Następnie korzystanie ze sparametryzowanych zapytań często zapewnia znaczne korzyści w zakresie wydajności.

mustaccio
źródło
Myślę, że taka jest teoria (oparta na użytych przygotowanych instrukcjach dla sparametryzowanych zapytań). W praktyce wątpię, by tak się często zdarzało, ponieważ większość implementacji po prostu przygotowuje-wiąże-wykonuje w jednym wywołaniu, więc użyj innej przygotowanej instrukcji dla każdego sparametryzowanego zapytania, chyba że podejmiesz wyraźne kroki w celu przygotowania instrukcji (i biblioteki -level prepareczęsto różni się od rzeczywistego poziomu SQL prepare).
jcaron
Następujące zapytania różnią się również od analizatora składni SQL: SELECT * FROM employees WHERE last_name IN (?, ?)i SELECT * FROM employees WHERE last_name IN (?, ?, ?, ?, ?, ?).
Damian Yerrick
Tak, oni mają. Dlaczego MS dodało buforowanie planu zapytań w 1998 roku do SQL Server 7. Jak w: Twoje informacje są starsze pokolenie.
TomTom
1
@TomTom - buforowanie planu zapytania nie jest tym samym, co autoparametryzacja, na którą wydajesz się sugerować. Jak w, przeczytaj przed opublikowaniem.
mustaccio
@mustaccio Właściwie przynajmniej stwardnienie rozsiane wprowadziło oba jednocześnie.
TomTom
5

Czekaj ale dlaczego?

Opcja 1 oznacza, że ​​musisz pisać procedury dezynfekujące dla każdego rodzaju danych wejściowych, podczas gdy opcja 2 jest mniej podatna na błędy i mniej kodu do pisania / testowania / obsługi.

Niemal na pewno „załatwienie wszystkich ostrzeżeń” może być bardziej złożone niż myślisz, a Twój język (na przykład Java PreparedStatement) ma więcej pod maską, niż myślisz.

Przygotowane instrukcje lub sparametryzowane zapytania są wstępnie kompilowane na serwerze bazy danych, więc po ustawieniu parametrów nie jest wykonywana konkatenacja SQL, ponieważ zapytanie nie jest już ciągiem SQL. Dodatkową zaletą jest to, że RDBMS buforuje zapytanie, a kolejne wywołania są uważane za ten sam SQL, nawet jeśli wartości parametrów się zmieniają, natomiast w przypadku łączonego SQL za każdym razem, gdy zapytanie jest uruchamiane z różnymi wartościami, zapytanie jest różne i RDBMS musi je przeanalizować , utwórz ponownie plan wykonania itp.

Tulains Córdova
źródło
1
JDBC nie dezynfekuje anithingów. Protokół ma określoną część dla parametru, a DB po prostu nie interpretuje tych parametrów, dlatego można ustawić nazwę tabeli na podstawie parametru.
talex
1
Dlaczego? jeśli parametr nie zostanie przeanalizowany ani zinterpretowany, nie ma powodu, aby coś uciekać.
talex
11
Myślę, że masz zły obraz tego, jak działa sparametryzowane zapytanie. Nie chodzi tylko o to, że parametry są później zastępowane, nigdy nie są zastępowane . DBMS przekształca każde zapytanie w „plan”, zestaw kroków, które zamierza wykonać, aby uzyskać wynik; w sparametryzowanym zapytaniu plan ten jest jak funkcja: zawiera szereg zmiennych, które należy podać podczas jego wykonywania. Do czasu dostarczenia zmiennych łańcuch SQL został całkowicie zapomniany, a plan jest właśnie wykonywany z podanymi wartościami.
IMSoP,
2
@IMSoP To było moje błędne przekonanie. Chociaż myślę, że to wspólna jeden jak widać w dwóch najbardziej głosowali odpowiedzi na to pytanie w SO stackoverflow.com/questions/3271249/... . Przeczytałem o tym i masz rację. Zredagowałem odpowiedź.
Tulains Córdova
3
@TomTom To świetne rozwiązanie dla wydajności , ale nie robi nic dla bezpieczeństwa . Do czasu skompilowania i buforowania zagrożonego fragmentu dynamicznego SQL program został już zmieniony . Tworzenie planu z niedynamicznego sparametryzowanego SQL, a następnie przekazywanie elementów danych nadal zasadniczo różni się od DBMS, abstrahując podobieństwo między dwoma zapytaniami przedstawionymi mu jako kompletnymi ciągami SQL.
IMSoP,
1

Wyobraźmy sobie, jak wyglądałoby idealne podejście „dezynfekcji, filtrowania i kodowania”.

Odkażanie i filtrowanie może mieć sens w kontekście konkretnej aplikacji, ale ostatecznie oba sprowadzają się do powiedzenia „nie możesz umieścić tych danych w bazie danych”. Dla twojej aplikacji może to być dobry pomysł, ale nie jest to coś, co można polecić jako ogólne rozwiązanie, ponieważ będą aplikacje, które będą mogły przechowywać dowolne znaki w bazie danych.

Więc to pozostawia kodowanie. Możesz zacząć od posiadania funkcji, która koduje łańcuchy poprzez dodawanie znaków zmiany znaczenia, abyś mógł je zastąpić w sobie. Ponieważ różne bazy danych potrzebne różne znaki ucieczki (w niektórych baz danych, zarówno \'i ''są ważne dla sekwencje escape ', ale nie w innych), funkcja ta musi być dostarczone przez producenta bazy danych.

Ale nie wszystkie zmienne są łańcuchami. Czasami musisz zastąpić liczbą całkowitą lub datą. Są one reprezentowane inaczej niż ciągi, więc potrzebujesz różnych metod kodowania (znowu, powinny one być specyficzne dla dostawcy bazy danych) i musisz zastąpić je zapytaniem na różne sposoby.

Być może więc byłoby łatwiej, gdyby baza danych obsłużyła również dla Ciebie podstawienie - już wie, jakie typy oczekuje zapytanie i jak bezpiecznie kodować dane oraz jak bezpiecznie je podstawić w zapytaniu, więc nie musisz się martwić to w twoim kodzie.

W tym momencie właśnie wymyśliliśmy sparametryzowane zapytania.

Po sparametryzowaniu zapytań otwierają się nowe możliwości, takie jak optymalizacja wydajności i uproszczone monitorowanie.

Kodowanie jest trudne do zrobienia dobrze, a kodowanie wykonane poprawnie jest nie do odróżnienia od parametryzacji.

Jeśli naprawdę lubisz interpolacji smyczkowy jako sposób zapytaniami budowlanych, istnieje kilka języków (Scala i ES2015 przyjść do głowy), które mają podłączany interpolacji ciąg, więc nie biblioteki , które pozwalają pisać parametryzacji zapytań, które wyglądają jak interpolacji smyczkowy, ale są bezpieczne przed iniekcją SQL - więc w składni ES2015:

import {sql} from 'cool-sql-library'

let result = sql`select *
    from users
    where user_id = ${user_id}
      and password_hash = ${password_hash}`.execute()

console.log(result)
James_pic
źródło
1
„Kodowanie jest trudne do zrobienia dobrze” - hahaha. Nie jest. Dzień lub dwa, wszystko jest udokumentowane. Koder napisałem wiele lat temu dla ORM (ponieważ serwer SQL ma ograniczenie parametrów i dlatego problematyczne jest wstawienie 5000-10000 wierszy w jednym wyciągu (15 lat temu). Nie pamiętam, żeby był to duży problem.
TomTom
1
Być może SQL Server jest na tyle regularny, że nie stanowi problemu, ale napotkałem problemy w innych bazach danych - przypadki narożne z niedopasowanym kodowaniem znaków, niejasne opcje konfiguracji, problemy z datą i liczbą specyficzne dla ustawień regionalnych. Wszystko rozwiązalne, ale wymagające choćby pobieżnego zrozumienia dziwactwa DB (patrzę na ciebie, MySQL i Oracle).
James_pic
3
@TomTom Kodowanie jest naprawdę bardzo trudne, gdy weźmiesz pod uwagę czas. Co robisz, gdy sprzedawca DB postanawia utworzyć nowy styl komentarzy w następnej wersji lub gdy słowo kluczowe staje się nowym słowem kluczowym w aktualizacji? Teoretycznie możesz uzyskać kodowanie właściwe dla jednej wersji RDBMS i pomylić się przy następnej wersji. Nawet nie zaczynaj, co się stanie, gdy zmienisz sprzedawcę na takiego, który ma komentarze warunkowe przy użyciu niestandardowej składni
Eric,
@Eric, to jest naprawdę przerażające. (Używam Postgres; jeśli ma takie dziwne brodawki, to jeszcze ich nie spotkałem.)
Wildcard
0

W opcji 1 pracujesz z zestawem wejściowym size = nieskończoność, który próbujesz zmapować na bardzo duży rozmiar wyjściowy. W opcji 2 ograniczyłeś swój wkład do wszystkiego, co wybierzesz. Innymi słowy:

  1. Starannie sprawdzaj i filtruj [ nieskończoność ] w poszukiwaniu [ wszystkich bezpiecznych zapytań SQL ]
  2. Korzystanie z [ wstępnie rozpatrzonych scenariuszy ograniczonych do Twojego zakresu ]

Według innych odpowiedzi, wydaje się, że ograniczenie wydajności z nieskończoności w kierunku czegoś, co da się zarządzać, również przynosi pewne korzyści.

Mutant Platypus
źródło
0

Jednym z przydatnych modeli mentalnych SQL (szczególnie nowoczesnych dialektów) jest to, że każda instrukcja SQL lub zapytanie jest programem. W rodzimym binarnym programie wykonywalnym najbardziej niebezpiecznymi rodzajami luk w zabezpieczeniach są przepełnienia, w których osoba atakująca może zastąpić lub zmodyfikować kod programu za pomocą różnych instrukcji.

Luka związana z iniekcją SQL jest izomorficzna dla przepełnienia bufora w języku takim jak C. Historia pokazała, że ​​przepełnienie bufora jest niezwykle trudne do uniknięcia - nawet bardzo krytyczny kod podlegający otwartemu przeglądowi często zawiera takie luki.

Jednym z ważnych aspektów nowoczesnego podejścia do rozwiązywania problemów związanych z przepełnieniem jest użycie sprzętu i mechanizmów systemu operacyjnego do oznaczania określonych części pamięci jako niewykonywalnych oraz do oznaczania innych części pamięci jako tylko do odczytu. (Zobacz na przykład artykuł z Wikipedii na temat ochrony przestrzeni wykonywalnej .) W ten sposób, nawet jeśli osoba atakująca może zmodyfikować dane, osoba atakująca nie może spowodować, że wprowadzone dane będą traktowane jak kod.

Więc jeśli luka w iniekcji SQL jest równoważna przepełnieniu bufora, to co to jest odpowiednik bitu NX lub stron pamięci tylko do odczytu? Odpowiedź brzmi: przygotowane instrukcje , które zawierają sparametryzowane zapytania oraz podobne mechanizmy dla zapytań innych niż zapytania. Przygotowana instrukcja jest kompilowana z niektórymi częściami oznaczonymi jako tylko do odczytu, więc osoba atakująca nie może zmienić tych części programu, a także innych części oznaczonych jako dane niewykonalne (parametry przygotowanej instrukcji), do których osoba atakująca mogłaby wprowadzić dane, ale który nigdy nie będzie traktowany jako kod programu, eliminując w ten sposób większość potencjalnych nadużyć.

Oczywiście odkażanie danych wejściowych użytkownika jest dobre, ale aby być naprawdę bezpiecznym, musisz być paranoikiem (lub, równoważnie, myśleć jak atakujący). Sposobem na to jest powierzchnia kontrolna poza tekstem programu , a przygotowane instrukcje zapewniają tę powierzchnię kontrolną dla SQL. Nie powinno więc dziwić, że przygotowane wypowiedzi, a więc sparametryzowane zapytania, są podejściem zalecanym przez zdecydowaną większość specjalistów ds. Bezpieczeństwa.

Daniel Pryden
źródło
To wszystko jest miłe i eleganckie, ale w ogóle nie odnosi się do pytania zgodnie z tytułem.
TomTom
1
@TomTom: Co masz na myśli? Pytanie dotyczy dokładnie, dlaczego sparametryzowane zapytania są preferowanym mechanizmem zapobiegania wstrzykiwaniu SQL; moja odpowiedź wyjaśnia, dlaczego sparametryzowane zapytania są bardziej bezpieczne i niezawodne niż dezynfekujące dane wejściowe użytkownika.
Daniel Pryden
Przykro mi, ale MOJE pytanie brzmi: „Dlaczego mechanizm zapobiegania iniekcji SQL ewoluował w kierunku używania zapytań sparametryzowanych?”. Oni nie. Tu nie chodzi o teraz, chodzi o historię.
TomTom
0

Już o tym piszę tutaj: https://stackoverflow.com/questions/6786034/can-parameterized-statement-stop-all-sql-injection/33033576#33033576

Ale dla uproszczenia:

Sposób działania sparametryzowanych zapytań polega na tym, że sqlQuery jest wysyłany jako zapytanie, a baza danych dokładnie wie, co zrobi to zapytanie, i dopiero wtedy wstawi nazwę użytkownika i hasło jedynie jako wartości. Oznacza to, że nie mogą one wpływać na zapytanie, ponieważ baza danych już wie, co zrobi zapytanie. W tym przypadku szukałby nazwy użytkownika „Nikt OR 1 = 1” - ”i pustego hasła, które powinno być fałszywe.

Nie jest to jednak kompletne rozwiązanie i nadal trzeba będzie dokonać weryfikacji danych wejściowych, ponieważ nie wpłynie to na inne problemy, takie jak ataki XSS, ponieważ nadal można umieścić javascript w bazie danych. Następnie, jeśli zostanie to odczytane na stronie, wyświetli się jako zwykły javascript, w zależności od sprawdzania poprawności danych wyjściowych. Tak więc naprawdę najlepszą rzeczą jest nadal sprawdzanie poprawności danych wejściowych, ale stosowanie sparametryzowanych zapytań lub procedur przechowywanych w celu zatrzymania wszelkich ataków SQL

Josip Ivic
źródło
0

Nigdy nie korzystałem z SQL. Ale oczywiście słyszysz o tym, jakie problemy mają ludzie, a programiści SQL mieli problemy z tą „iniekcją SQL”. Przez długi czas nie mogłem tego rozgryźć. I wtedy zdałem sobie sprawę, że ludzie, którzy tworzą instrukcje SQL, są prawdziwymi tekstowymi instrukcjami źródłowymi SQL, łącząc łańcuchy, z których niektóre są wprowadzane przez użytkownika. A moją pierwszą myślą o tej realizacji był szok. Całkowity szok. Pomyślałem: jak ktokolwiek może być tak absurdalnie głupi i tworzyć wypowiedzi w dowolnym języku programowania? Dla programisty C, C ++, Java lub Swift jest to kompletne szaleństwo.

To powiedziawszy, nie jest bardzo trudne napisanie funkcji C, która przyjmuje ciąg C jako argument i tworzy inny ciąg, który wygląda dokładnie jak literał ciągu w kodzie źródłowym C, który reprezentuje ten sam ciąg. Na przykład ta funkcja przetłumaczy abc na „abc”, a „abc” na „\” abc \ ”” i „\” abc \ ”„ na ”\" \\ "abc \\" \ "". (Cóż, jeśli ci to nie wygląda, to jest html. To było właściwe, kiedy go wpisałem, ale nie, kiedy się wyświetli) A kiedy ta funkcja C zostanie napisana, generowanie kodu źródłowego C nie jest trudne tekst z pola wejściowego dostarczonego przez użytkownika zamieniany jest na literał „C”. To nie jest trudne do zapewnienia bezpieczeństwa. Dlaczego programiści SQL nie używają tego podejścia jako sposobu na uniknięcie zastrzyków SQL, jestem poza moim zasięgiem.

„Odkażanie” jest całkowicie błędne. Fatalna wada polega na tym, że powoduje, że niektóre dane wejściowe użytkownika są nielegalne. W efekcie powstaje baza danych, w której ogólne pole tekstowe nie może zawierać tekstu podobnego do; Upuść tabelę lub cokolwiek, co byś użył we wstrzyknięciu SQL, aby spowodować uszkodzenie. Uważam to za niedopuszczalne. Jeśli baza danych przechowuje tekst, powinna móc przechowywać dowolny tekst. A praktyczną wadą jest to, że środek dezynfekujący nie wydaje się robić tego poprawnie :-(

Oczywiście sparametryzowane zapytania są tym, czego oczekiwałby każdy programista używający skompilowanego języka. To sprawia, że ​​życie jest o wiele łatwiejsze: masz jakiś ciąg znaków i nigdy nawet nie zadajesz sobie trudu, aby przetłumaczyć go na ciąg SQL, ale po prostu przekaż go jako parametr, bez szansy, że jakiekolwiek znaki w tym ciągu spowodują jakiekolwiek uszkodzenie.

Z punktu widzenia programisty używającego skompilowanych języków dezynfekcja jest czymś, co nigdy nie przyszło mi do głowy. Potrzeba dezynfekcji jest szalona. Zapytania sparametryzowane są oczywistym rozwiązaniem problemu.

(Uważam, że odpowiedź Josipa jest interesująca. Zasadniczo mówi, że dzięki sparametryzowanym zapytaniom możesz zatrzymać każdy atak na SQL, ale potem możesz mieć tekst w bazie danych, który jest używany do utworzenia zastrzyku JavaScript :-( Cóż, znowu mamy ten sam problem i nie wiem, czy JavaScript ma na to rozwiązanie.

gnasher729
źródło
-2

Głównym problemem jest to, że hakerzy znaleźli sposoby na otoczenie urządzeń sanitarnych, podczas gdy sparametryzowane zapytania stanowiły istniejącą procedurę, która działała idealnie z dodatkowymi korzyściami związanymi z wydajnością i pamięcią.

Niektóre osoby upraszczają problem, ponieważ „jest to tylko pojedynczy cytat i podwójny cytat”, ale hakerzy znaleźli sprytne sposoby na uniknięcie wykrycia, takie jak użycie różnych kodowań lub korzystanie z funkcji bazy danych.

W każdym razie wystarczyło zapomnieć o jednym łańcuchu, aby stworzyć katastrofalne naruszenie danych. Hakerzy mogli zautomatyzować skrypty, aby pobrać całą bazę danych z serią lub zapytaniami. Jeśli oprogramowanie jest dobrze znane jako pakiet open source lub słynny pakiet biznesowy, możesz po prostu przejrzeć tabelę użytkowników i haseł.

Z drugiej strony po prostu używanie połączonych zapytań było tylko kwestią nauki używania i przyzwyczajania się do nich.

Borjab
źródło