Z mojego punktu widzenia atakom wstrzykiwania SQL można zapobiec poprzez:
- Dokładne przeglądanie, filtrowanie, kodowanie danych wejściowych (przed wstawieniem do SQL)
- 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 queries
i encoding libraries
które są traktowane zamiennie. Popraw mnie, jeśli się mylę, ale mam wrażenie, że się różnią.
Rozumiem, że encoding libraries
bez 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?
Odpowiedzi:
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.
źródło
null
cią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!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.
źródło
O\'Malley
uż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 cytatemO''Malley
. Niezbyt przenośny, jeśli musisz to zrobić sam.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:
Jeśli ktoś sugeruje zrobienie tego, słusznie odpowiedziałbyś, że jest to raczej niedorzeczne i że należy po prostu:
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:
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ś.
źródło
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.
źródło
Mówiąc wprost: nie zrobili tego. Twoje oświadczenie:
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.
źródło
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.
źródło
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'
iselect * 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.
źródło
prepare
często różni się od rzeczywistego poziomu SQLprepare
).SELECT * FROM employees WHERE last_name IN (?, ?)
iSELECT * FROM employees WHERE last_name IN (?, ?, ?, ?, ?, ?)
.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.
źródło
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 są 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:
źródło
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:
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.
źródło
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.
źródło
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
źródło
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.
źródło
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.
źródło