W jaki sposób PreparedStatement zapobiega lub zapobiega iniekcji SQL?

122

Wiem, że PreparedStatements unikają / zapobiegają iniekcji SQL. Jak to się dzieje? Czy kwerenda w postaci ostatecznej, która została utworzona przy użyciu PreparedStatements, będzie ciągiem znaków, czy w inny sposób?

Prabhu R
źródło
3
Technicznie specyfikacja JDBC nie twierdzi, że nie ma błędów wstrzykiwania SQL. Nie znam żadnych dysków, których to dotyczy.
Tom Hawtin - tackline
3
@Jayesh Proponuję dodać tutaj zawartość swojego bloga jako odpowiedź. Większość odpowiedzi mówi tylko o różnicach między generowaniem dynamicznych zapytań SQL a przygotowanym zestawieniem. Nie zajmują się tym, dlaczego przygotowane wypowiedzi działają lepiej, a Twój blog.
Pavan Manjunath
1
Dodano jako odpowiedź, mam nadzieję, że to pomoże.
Jayesh

Odpowiedzi:

78

Problem z iniekcją SQL polega na tym, że dane wejściowe użytkownika są używane jako część instrukcji SQL. Używając przygotowanych instrukcji, możesz wymusić, aby dane wejściowe użytkownika były traktowane jako zawartość parametru (a nie jako część polecenia SQL).

Ale jeśli nie używasz danych wejściowych użytkownika jako parametru dla przygotowanej instrukcji, ale zamiast tego zbudujesz polecenie SQL, łącząc ze sobą ciągi znaków, nadal jesteś podatny na iniekcje SQL, nawet jeśli używasz przygotowanych instrukcji.

tangens
źródło
1
Jasne, ale nadal możesz zakodować na stałe niektóre lub wszystkie parametry.
tangens
16
Przykład proszę - ale jeśli nie używasz danych wejściowych użytkownika jako parametru dla przygotowanej instrukcji, ale zamiast tego zbudujesz polecenie SQL, łącząc ze sobą ciągi znaków, nadal jesteś podatny na iniekcje SQL, nawet jeśli używasz przygotowanych instrukcji.
david Blaine
4
Przygotowane instrukcje FWIW to nie JDBC - to SQL. Przygotowane instrukcje możesz przygotowywać i wykonywać z poziomu konsoli SQL. PreparedStatement obsługuje je tylko z poziomu JDBC.
beldaz
198

Rozważ dwa sposoby zrobienia tego samego:

PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();

Lub

PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)");
stmt.setString(1, user);
stmt.execute();

Jeśli „użytkownik” pochodzi z danych wejściowych użytkownika, a dane wejściowe użytkownika to

Robert'); DROP TABLE students; --

Wtedy, w pierwszej kolejności, zostaniesz uwięziony. Po drugie, będziesz bezpieczny i Little Bobby Tables będzie zarejestrowany w Twojej szkole.

Paul Tomblin
źródło
8
Tak więc, jeśli dobrze zrozumiałem, zapytanie w drugim przykładzie, które zostanie wykonane, wyglądałoby tak: INSERT INTO student VALUES ("Robert"); DROP TABLE studentów; - ") - lub przynajmniej coś takiego. Czy to prawda?
Maksymalnie
18
Nie, w PIERWSZYM przypadku otrzymasz to oświadczenie. W drugim wstawiłby „Robert”); DROP TABLE studentów; - ”do tabeli użytkownika.
Paul Tomblin
3
To właśnie miałem na myśli, w drugim przykładzie („bezpiecznym”), ciąg Robert '); DROP TABLE studenci; - zostanie zapisany w polu w tabeli uczniów. Napisałem coś innego? ;)
Max
7
Przepraszamy, zagnieżdżanie cudzysłowów to coś, czego staram się unikać z powodu takiego zamieszania. Dlatego lubię PreparedStatements z parametrami.
Paul Tomblin
59
Stoły Little Bobby. XD Świetne odniesienie
Amalgovinus
130

Aby zrozumieć, w jaki sposób PreparedStatement zapobiega iniekcji SQL, musimy zrozumieć fazy wykonywania zapytania SQL.

1. Faza kompilacji. 2. Faza wykonania.

Za każdym razem, gdy silnik serwera SQL otrzymuje zapytanie, musi przejść przez poniższe fazy,

Fazy ​​wykonywania zapytań

  1. Faza analizowania i normalizacji: W tej fazie zapytanie jest sprawdzane pod kątem składni i semantyki. Sprawdza, czy tabela odwołań i kolumny używane w zapytaniu istnieją, czy nie. Ma też wiele innych zadań do wykonania, ale nie omawiajmy szczegółów.

  2. Faza kompilacji: W tej fazie słowa kluczowe używane w zapytaniu, takie jak wybierz, skąd itp. Są konwertowane na format zrozumiały dla maszyny. Jest to faza, w której zapytanie jest interpretowane i podejmowane są odpowiednie działania. Ma też wiele innych zadań do wykonania, ale nie omawiajmy szczegółów.

  3. Plan optymalizacji zapytań: w tej fazie tworzone jest drzewo decyzyjne w celu znalezienia sposobów wykonania zapytania. Dowiaduje się, ile sposobów można wykonać zapytanie oraz koszt związany z każdym sposobem wykonania zapytania. Wybiera najlepszy plan wykonania zapytania.

  4. Pamięć podręczna: najlepszy plan wybrany w planie optymalizacji zapytań jest przechowywany w pamięci podręcznej, dzięki czemu za każdym razem, gdy pojawi się to samo zapytanie, nie musi ponownie przechodzić przez fazę 1, fazę 2 i fazę 3. Kiedy następnym razem pojawi się zapytanie, zostanie sprawdzone bezpośrednio w pamięci podręcznej i stamtąd pobrane do wykonania.

  5. Faza wykonania: w tej fazie dostarczone zapytanie zostaje wykonane, a dane są zwracane do użytkownika jako ResultSetobiekt.

Zachowanie interfejsu API PreparedStatement w powyższych krokach

  1. PreparedStatements nie są kompletnymi zapytaniami SQL i zawierają symbole zastępcze, które w czasie wykonywania są zastępowane przez rzeczywiste dane podane przez użytkownika.

  2. Ilekroć jakikolwiek PreparedStatment zawierający symbole zastępcze jest przekazywany do silnika SQL Server, przechodzi przez poniższe fazy

    1. Faza analizy i normalizacji
    2. Faza kompilacji
    3. Plan optymalizacji zapytań
    4. Pamięć podręczna (skompilowane zapytanie z symbolami zastępczymi są przechowywane w pamięci podręcznej).

UPDATE user set nazwa_użytkownika =? i hasło =? GDZIE id =?

  1. Powyższe zapytanie zostanie przeanalizowane, skompilowane z symbolami zastępczymi w ramach specjalnego traktowania, zoptymalizowane i zbuforowane. Zapytanie na tym etapie jest już kompilowane i konwertowane w formacie zrozumiałym dla komputera. Możemy więc powiedzieć, że zapytanie przechowywane w pamięci podręcznej jest wstępnie skompilowane i tylko symbole zastępcze muszą zostać zastąpione danymi dostarczonymi przez użytkownika.

  2. Teraz w czasie wykonywania, gdy przychodzą dane dostarczone przez użytkownika, wstępnie skompilowane zapytanie jest pobierane z pamięci podręcznej, a symbole zastępcze są zastępowane danymi dostarczonymi przez użytkownika.

PrepareStatementWorking

(Pamiętaj, że po zastąpieniu znaków miejsca danymi użytkownika ostateczne zapytanie nie jest ponownie kompilowane / interpretowane, a silnik SQL Server traktuje dane użytkownika jako czyste dane, a nie SQL, który musi być ponownie przeanalizowany lub skompilowany; to jest piękno PreparedStatement. )

Jeśli zapytanie nie musi ponownie przechodzić przez fazę kompilacji, wówczas wszelkie dane zastąpione w elementach zastępczych są traktowane jako czyste dane i nie mają znaczenia dla silnika SQL Server i bezpośrednio wykonuje zapytanie.

Uwaga: Jest to faza kompilacji po fazie analizy, która rozumie / interpretuje strukturę zapytania i nadaje jej sensowne zachowanie. W przypadku PreparedStatement zapytanie jest kompilowane tylko raz, a buforowane zapytanie skompilowane jest cały czas pobierane w celu zastąpienia danych użytkownika i wykonania.

Dzięki jednorazowej funkcji kompilacji programu PreparedStatement jest on wolny od ataku SQL Injection.

Możesz uzyskać szczegółowe wyjaśnienie z przykładem tutaj: https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html

Jayesh
źródło
3
miłe wyjaśnienie
Dheeraj Joshi
4
Dosłownie najbardziej kompletna odpowiedź na temat JAK to działa
jouell
To było bardzo pomocne. Dzięki za szczegółowe wyjaśnienie.
Nieznane
26

Kod SQL używany w PreparedStatement jest prekompilowany w sterowniku. Od tego momentu parametry są wysyłane do sterownika jako wartości literalne, a nie wykonywalne fragmenty kodu SQL; dlatego nie można wstrzyknąć kodu SQL za pomocą parametru. Innym korzystnym efektem ubocznym PreparedStatements (prekompilacja + wysyłanie tylko parametrów) jest poprawiona wydajność podczas wielokrotnego uruchamiania instrukcji, nawet z różnymi wartościami parametrów (przy założeniu, że sterownik obsługuje PreparedStatements), ponieważ sterownik nie musi wykonywać analizy SQL i kompilacji każdego z nich czas zmiany parametrów.

Travis Heseman
źródło
Nie trzeba tego wdrażać w ten sposób i wydaje mi się, że często tak nie jest.
Tom Hawtin - tackline
4
W rzeczywistości kod SQL jest zwykle prekompilowany w bazie danych. Oznacza to, że w bazie danych jest przygotowywany plan wykonania. Podczas wykonywania zapytania plan jest wykonywany z tymi parametrami. Dodatkową korzyścią jest to, że ta sama instrukcja może zostać wykonana z różnymi parametrami bez konieczności kompilowania przez procesor zapytań za każdym razem nowego planu.
beldaz
3

Myślę, że to będzie sznurek. Ale parametry wejściowe zostaną przesłane do bazy danych i odpowiednie rzutowanie / konwersje zostaną zastosowane przed utworzeniem rzeczywistej instrukcji SQL.

Aby dać ci przykład, może spróbować sprawdzić, czy CAST / Conversion działa.
Jeśli to zadziała, może stworzyć z tego końcowe oświadczenie.

   SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))

Wypróbuj przykład z instrukcją SQL akceptującą parametr numeryczny.
Teraz spróbuj przekazać zmienną łańcuchową (z zawartością liczbową, którą można zaakceptować jako parametr numeryczny). Czy powoduje to jakiś błąd?

Teraz spróbuj przekazać zmienną łańcuchową (z treścią, której nie można zaakceptować jako parametr numeryczny). Zobacz co się dzieje?

shahkalpesh
źródło
3

Przygotowane zestawienie jest bezpieczniejsze. Konwertuje parametr na określony typ.

Na przykład stmt.setString(1, user);przekonwertuje userparametr na ciąg.

Załóżmy, że parametr zawiera ciąg SQL zawierający wykonywalne polecenie : użycie przygotowanej instrukcji na to nie pozwoli.

Dodaje do tego metaznak (czyli automatyczną konwersję).

To sprawia, że ​​jest bezpieczniejszy.

Guru R Handa
źródło
2

Wstrzyknięcie SQL: gdy użytkownik ma szansę wprowadzić coś, co mogłoby być częścią instrukcji sql

Na przykład:

Zapytanie ciągowe = „WSTAW WARTOŚCI DO uczniów ('” + użytkownik + „')”

gdy użytkownik wprowadzi „Robert”); DROP TABLE studenci; - ”jako wejście powoduje iniekcję SQL

Jak przygotowane oświadczenie temu zapobiega?

Zapytanie ciągowe = „WSTAW DO uczniów WARTOŚCI ('” + „: nazwa” + „')”

parameters.addValue („nazwa”, użytkownik);

=> gdy użytkownik ponownie wprowadzi „Robert”); DROP TABLE studenci; - „, ciąg wejściowy jest prekompilowany w sterowniku jako wartości dosłowne i myślę, że może być rzutowany na przykład:

CAST („Robert”); DROP TABLE studenci; - „AS varchar (30))

Na końcu ciąg zostanie dosłownie wstawiony jako nazwa do tabeli.

http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/

Jacek
źródło
1
Jeśli się nie mylę, część CAST(‘Robert’);z CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))pękłaby, a następnie spadłby stół, gdyby tak było. Zatrzymuje zastrzyk, więc uważam, że przykład nie jest wystarczająco kompletny, aby wyjaśnić scenariusz.
Héctor Álvarez
1

Przygotowane oświadczenie:

1) Wstępna kompilacja i buforowanie instrukcji SQL po stronie DB prowadzi do ogólnego szybszego wykonywania i możliwości ponownego użycia tej samej instrukcji SQL w partiach.

2) Automatyczne zapobieganie atakom polegającym na iniekcji SQL poprzez wbudowane znaki cytowania i inne znaki specjalne. Zauważ, że wymaga to użycia dowolnej metody PreparedStatement setXxx () do ustawienia wartości.

Mukesh Kumar
źródło
1

Jak wyjaśniono w tym poście , PreparedStatementsamo to nie pomoże, jeśli nadal łączysz ciągi znaków.

Na przykład jeden nieuczciwy napastnik nadal może wykonać następujące czynności:

  • wywołać funkcję uśpienia, aby wszystkie połączenia z bazą danych były zajęte, co spowoduje, że aplikacja będzie niedostępna
  • wyodrębnianie wrażliwych danych z bazy danych
  • omijanie uwierzytelniania użytkownika

Nie tylko SQL, ale nawet JPQL lub HQL mogą zostać naruszone, jeśli nie używasz parametrów wiązania.

Podsumowując, nigdy nie należy używać konkatenacji ciągów podczas tworzenia instrukcji SQL. Skorzystaj w tym celu z dedykowanego API:

Vlad Mihalcea
źródło
1
Dziękujemy za wskazanie, jak ważne jest używanie wiązania parametrów, a nie samego PreparedStatement. Twoja odpowiedź wydaje się jednak sugerować, że użycie dedykowanego interfejsu API jest niezbędne do ochrony przed iniekcją SQL. Ponieważ tak nie jest, a użycie PreparedStatement z wiązaniem parametrów również działa, czy chciałbyś zmienić skład?
Dziki Pottok
-3

W przygotowanych zestawieniach użytkownik jest zmuszony do wprowadzania danych jako parametrów. Jeśli użytkownik wprowadzi wrażliwe instrukcje, takie jak DROP TABLE lub SELECT * FROM USERS, nie wpłynie to na dane, ponieważ będą one traktowane jako parametry instrukcji SQL

Shreyas K.
źródło
Taka sama odpowiedź jak wybrana odpowiedź z mniejszą dokładnością.
Julien Maret