Wstrzykiwanie SQL jest bardzo poważnym problemem bezpieczeństwa, w dużej mierze dlatego, że tak łatwo go pomylić: oczywisty, intuicyjny sposób budowania zapytania zawierającego dane wejściowe użytkownika naraża cię na niebezpieczeństwo, a właściwy sposób jego złagodzenia wymaga znajomości sparametryzowanej najpierw zapytania i wstrzyknięcie SQL.
Wydaje mi się, że oczywistym sposobem na rozwiązanie tego problemu byłoby wyłączenie oczywistej (ale niepoprawnej) opcji: napraw silnik bazy danych, aby każde otrzymane zapytanie, które używa zakodowanych wartości w klauzuli WHERE zamiast parametrów, zwróciło ładny, opisowy komunikat o błędzie instruujący zamiast tego użyć parametrów. Musiałoby to oczywiście wymagać opcji rezygnacji, aby takie rzeczy, jak zapytania ad-hoc z narzędzi administracyjnych nadal działały łatwo, ale powinny być domyślnie włączone.
Takie zamknięcie spowodowałoby zastrzyk SQL na zimno, prawie z dnia na dzień, ale o ile mi wiadomo, żaden RDBMS tak naprawdę nie robi. Czy jest jakiś dobry powód, dlaczego nie?
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'
miałby zarówno zakodowane na stałe, jak i sparametryzowane wartości w jednym zapytaniu - spróbuj to złapać! Myślę, że istnieją uzasadnione przypadki użycia dla takich mieszanych zapytań.SELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
"bad"
jest naprawdę dosłowny, czy też wynika z połączenia łańcucha. Dwa rozwiązania, które widzę, to albo pozbywanie się SQL i innych DSL osadzonych na łańcuchach (tak, proszę), albo promowanie języków, w których łączenie łańcuchów jest bardziej irytujące niż używanie sparametryzowanych zapytań (umm, nie).Odpowiedzi:
Jest zbyt wiele przypadków, w których użycie literału jest właściwym podejściem.
Z punktu widzenia wydajności zdarza się, że w zapytaniach chcesz literałów. Wyobraź sobie, że mam narzędzie do śledzenia błędów, w którym gdy będzie wystarczająco duże, aby martwić się wydajnością, spodziewam się, że 70% błędów w systemie zostanie „zamkniętych”, 20% będzie „otwartych”, 5% będzie „aktywnych”, a 5 % będzie miało inny status. Mogę zasadnie chcieć mieć zapytanie, które zwraca wszystkie aktywne błędy
zamiast przekazywać
status
jako zmienną wiążącą. Chcę inny plan zapytań w zależności od przekazanej wartościstatus
- chciałbym wykonać skanowanie tabeli, aby zwrócić zamknięte błędy i skanowanie indeksustatus
kolumna, aby zwrócić aktywne pożyczki. Teraz różne bazy danych i różne wersje mają różne podejścia, aby (mniej lub bardziej skutecznie) pozwolić temu samemu zapytaniu na użycie innego planu zapytań w zależności od wartości zmiennej powiązania. Ale to zwykle wprowadza przyzwoitą złożoność, którą należy zarządzać, aby zrównoważyć decyzję o tym, czy zawracać sobie głowę ponowną analizą zapytania, czy też ponownie wykorzystać istniejący plan dla nowej wartości zmiennej powiązania. Dla programisty rozsądne może być radzenie sobie z tą złożonością. Lub może mieć sens wymuszenie innej ścieżki, gdy mam więcej informacji o tym, jak będą wyglądać moje dane, niż optymalizator.Z punktu widzenia złożoności kodu jest wiele razy, że literały w instrukcjach SQL mają sens. Na przykład, jeśli masz
zip_code
kolumnę z 5-znakowym kodem pocztowym, a czasami ma dodatkowe 4 cyfry, warto zrobić coś takiegozamiast przekazywać 4 oddzielne parametry dla wartości liczbowych. Nie są to rzeczy, które kiedykolwiek się zmienią, więc ich powiązanie ze zmiennymi służy tylko do tego, że kod jest potencjalnie trudniejszy do odczytania i do stworzenia możliwości, że ktoś będzie wiązał parametry w niewłaściwej kolejności i zakończy się błędem.
źródło
Wstrzykiwanie SQL ma miejsce, gdy zapytanie jest budowane przez połączenie tekstu z niezaufanego i nieważnego źródła z innymi częściami zapytania. Podczas gdy coś takiego najczęściej występuje w literałach łańcuchowych, nie byłby to jedyny sposób, w jaki mogłoby się to zdarzyć. Zapytanie o wartości liczbowe może wymagać łańcucha wprowadzonego przez użytkownika (to znaczy powinien zawierać tylko cyfry) i połączyć się z innym materiałem, aby utworzyć zapytanie bez znaków cudzysłowu zwykle związanych z literałami ciągów; kod, który nadmiernie ufa sprawdzaniu poprawności po stronie klienta, może mieć na przykład nazwy pól pochodzące z ciągu zapytania HTML. Nie ma sposobu, aby kod patrząc na ciąg zapytania SQL mógł zobaczyć, jak został złożony.
Ważne jest nie to, czy instrukcja SQL zawiera literały łańcuchowe, ale raczej, czy łańcuch zawiera sekwencje znaków z niezaufanych źródeł , a sprawdzenie poprawności najlepiej byłoby przeprowadzić w bibliotece, która tworzy zapytania. Zasadniczo w języku C # nie ma sposobu na napisanie kodu, który pozwoli na literałość łańcucha, ale nie pozwoli na inne wyrażenia łańcuchowe, ale można mieć regułę kodowania, która wymaga budowania zapytań za pomocą klasy budującej zapytania, a nie konkatenacja ciągów i każdy, kto przekaże ciąg nieliteralny do konstruktora zapytań, musi uzasadnić takie działanie.
źródło
Jeśli chcesz umieścić wyniki z nich w stopce forum, musisz dodać fikcyjny parametr, aby za każdym razem mówić false. Albo naiwny programista internetowy sprawdza, jak wyłączyć to ostrzeżenie, a następnie kontynuuje.
Teraz możesz powiedzieć, że dodasz wyjątek dla wyliczeń, ale to po prostu ponownie otworzy dziurę (choć mniejszą). Nie wspominając już o tym, że ludzie muszą najpierw nauczyć się nie korzystać
varchars
.Prawdziwym problemem wstrzykiwania jest programowo konstruowanie ciągu zapytania. Rozwiązaniem tego jest mechanizm procedury składowanej i egzekwowanie jego użycia lub biała lista dozwolonych zapytań.
źródło
deleted = false
zNOT deleted
, co pozwala uniknąć dosłownym. Ale punkt jest ogólnie ważny.TL; DR : Musiałbyś ograniczyć wszystkie literały, nie tylko te w
WHERE
klauzulach. Z powodów, dla których tego nie robią, pozwala to na odłączenie bazy danych od innych systemów.Po pierwsze, twoje założenie jest wadliwe. Chcesz ograniczyć tylko
WHERE
klauzule, ale to nie jedyne miejsce, w którym użytkownik może wejść. Na przykład,Jest to równie podatne na wstrzyknięcie SQL:
Więc nie możesz po prostu ograniczyć literałów w
WHERE
klauzuli. Musisz ograniczyć wszystkie literały.Teraz pozostaje pytanie: „Po co w ogóle dopuszczać literałów?” Pamiętaj o tym: chociaż relacyjne bazy danych są używane pod aplikacją napisaną w innym języku przez znaczny procent czasu, nie ma wymagań używania kodu aplikacji do korzystania z bazy danych. I oto odpowiedź: potrzebujesz literałów, aby napisać kod. Jedyną inną alternatywą byłoby wymaganie, aby cały kod był napisany w jakimś języku niezależnym od bazy danych. Dzięki temu masz możliwość pisania „kodu” (SQL) bezpośrednio w bazie danych. Jest to cenne oddzielenie i bez literałów byłoby to niemożliwe. (Spróbuj pisać kiedyś w swoim ulubionym języku bez literałów. Jestem pewien, że możesz sobie wyobrazić, jak trudne byłoby to.)
Jako typowy przykład, literały są często używane w populacji tabel z listą wartości / tablic przeglądowych:
Bez nich musisz napisać kod w innym języku programowania, aby wypełnić tę tabelę. Możliwość zrobienia tego bezpośrednio w SQL jest cenna .
Pozostaje nam jeszcze jedno pytanie: dlaczego więc nie robią tego biblioteki klienta języka programowania? I tutaj mamy bardzo prostą odpowiedź: ponownie wdrożyliby cały parser bazy danych dla każdej obsługiwanej wersji bazy danych . Czemu? Ponieważ nie ma innego sposobu, aby zagwarantować, że znalazłeś każdy literał. Wyrażenia regularne to za mało. Na przykład: zawiera 4 oddzielne literały w PostgreSQL:
Próba zrobienia tego byłaby koszmarem konserwacyjnym, zwłaszcza że poprawna składnia często zmienia się między głównymi wydaniami baz danych.
źródło