Czy muszę chronić się przed wstrzyknięciem SQL, jeśli korzystam z listy rozwijanej?

96

Rozumiem, że NIGDY nie należy ufać wprowadzaniu danych przez użytkownika z formularza, głównie ze względu na możliwość wstrzyknięcia SQL.

Czy dotyczy to jednak również formularza, w którym jedyne dane wejściowe pochodzą z listy rozwijanej (patrz poniżej)?

Zapisuję $_POST['size']do sesji, która jest następnie używana w całej witrynie do wykonywania zapytań w różnych bazach danych (z rozszerzeniemmysqli zapytania Select), a każdy zastrzyk SQL na pewno je zaszkodzi (prawdopodobnie upuści).

Nie ma obszaru do wprowadzania danych wejściowych przez użytkownika w celu przeszukiwania baz danych, tylko menu rozwijane.

<form action="welcome.php" method="post">
<select name="size">
  <option value="All">Select Size</option> 
  <option value="Large">Large</option>
  <option value="Medium">Medium</option>
  <option value="Small">Small</option>
</select>
<input type="submit">
</form>
Łachmany
źródło
112
Tak. Nic nie stoi na przeszkodzie, aby osoba atakująca przesłała dowolne wartości w Twoich <select>danych wejściowych. Rzeczywiście, nawet mało techniczny użytkownik mógłby dodać dodatkowe opcje za pomocą konsoli przeglądarki. jeśli utrzymasz tablicę z białą listą dostępnych wartości i porównasz z nią dane wejściowe, możesz to złagodzić (i powinieneś, ponieważ zapobiega to niechcianym wartościom)
Michael Berkowski
13
Powinieneś zrozumieć podstawowe kwestie dotyczące żądań / odpowiedzi i to, że nie ma znaczenia, w jaki sposób front-end jest zbudowany na żądaniu, tj. W tym przypadku lista rozwijana
Royal Bg
13
@YourCommonSense Ponieważ to dobre pytanie. Nie każdy zdaje sobie sprawę, jak bardzo podatny jest klient. Spowoduje to bardzo cenne odpowiedzi na tę stronę.
Cruncher
8
@Cruncher widzę. Dla przeciętnego przełożonego stosów jest to nauka o rakietach, o której słyszeli wcześniej. Nawet pomimo najpopularniejszego pytania pod tagiem PHP.
Twój zdrowy rozsądek
9
„Rozumiem, że NIGDY nie należy ufać wkładowi użytkownika”. Bez wyjątków.
Prinzhorn

Odpowiedzi:

69

Możesz zrobić coś tak prostego, jak poniższy przykład, aby upewnić się, że opublikowany rozmiar jest zgodny z oczekiwaniami.

$possibleOptions = array('All', 'Large', 'Medium', 'Small');

if(in_array($_POST['size'], $possibleOptions)) {
    // Expected
} else {
    // Not Expected
}

Następnie użyj mysqli_ *, jeśli używasz wersji php> = 5.3.0, którą powinieneś być, aby zapisać wynik. Jeśli zostanie użyty poprawnie, pomoże to w iniekcji sql.

Oliver Bayes-Shelton
źródło
czy to jest skrócona wersja „białej listy” OliverBS?
Szmaty
Nie jest to tylko bardzo podstawowa wersja jednego z nich, możesz dodać wartości do bazy danych, aby sprawdzić, czy jest łatwiejsza i wielokrotnego użytku, lub może utworzyć klasę białej listy z określoną metodą dla każdej białej listy do sprawdzenia. jeśli nie chcesz używać bazy danych, właściwości białej listy mogą znajdować się wewnątrz właściwości tablicy w klasie białej listy.
Oliver Bayes-Shelton
9
Niemniej jednak musisz użyć przygotowanych instrukcji (zalecane) lub mysqli_real_escape_string. Również poprawnie zmieniaj znaczenie wartości podczas ich wyprowadzania (np. Użyj htmlspecialchars () w dokumencie HTML).
ComFreek
4
Sugerowałbym ustawienie trzeciego parametru na w in_arraycelu trueścisłego porównania. Nie jestem pewien, co mogło pójść nie tak, ale luźne porównanie jest dość dziwaczne.
Brilliand
1
Wartości @OliverBS $ _POST mogą być również tablicami. Ciągi liczbowe są porównywane jako liczby („5” == „05”). Nie sądzę, abyś miał lukę w zabezpieczeniach w swoim konkretnym przykładzie, ale zasady są złożone, a dziury mogą się otwierać z powodów, których nawet nie rozumiem. Ścisłe porównanie jest łatwiejsze do rozważenia, a zatem łatwiejsze w bezpiecznym użyciu.
Brilliand
195

Tak, musisz się przed tym chronić.

Pozwólcie, że pokażę wam dlaczego, używając konsoli programisty Firefoksa:

edytuję jedną z wartości w menu rozwijanym, aby była instrukcją tabeli rozwijanej

Jeśli nie wyczyścisz tych danych, Twoja baza danych zostanie zniszczona. (Może to nie być w pełni poprawna instrukcja SQL, ale mam nadzieję, że udało mi się zrozumieć).

To, że ograniczyłeś opcje dostępne w menu , nie oznacza , że ograniczyłeś dane, które mogę wysłać do twojego serwera.

Jeśli próbowałeś dalej ograniczyć to za pomocą zachowania na swojej stronie, moje opcje obejmują wyłączenie tego zachowania lub po prostu napisanie niestandardowego żądania HTTP do serwera, które i tak imituje ten formularz. Do tego celu służy narzędzie o nazwie curl i myślę, że polecenie przesłania tego wstrzyknięcia SQL i tak wyglądałoby mniej więcej tak:

curl --data "size=%27%29%3B%20DROP%20TABLE%20*%3B%20--"  http://www.example.com/profile/save

(To może nie być całkowicie poprawne polecenie zwijania, ale znowu mam nadzieję, że udało mi się zrozumieć.)

Więc powtórzę:

NIGDY nie ufaj wprowadzaniu danych przez użytkownika. ZAWSZE chroń siebie.

Nie zakładaj, że jakiekolwiek dane wejściowe użytkownika są kiedykolwiek bezpieczne. Jest to potencjalnie niebezpieczne, nawet jeśli dotrze w inny sposób niż formularz. Żadne z nich nie jest na tyle wiarygodne, aby zrezygnować z ochrony przed wstrzyknięciami SQL.

doppelgreener
źródło
24
Nie wspominając już o tworzeniu niestandardowego ładunku za pomocą curl. ZAWSZE oczyszczaj dane wejściowe po stronie serwera !
recursion.ninja
14
Obraz mówi więcej niż tysiąc słów.
davidkonrad
4
To powinna być domyślna odpowiedź. Powinien również zawierać coś o curl. Ludzie nie rozumieją, że możesz wysłać żądanie HTTP z dowolnego miejsca w dowolne miejsce, używając dowolnego formatu i przekazując dowolne wartości, to serwer musi upewnić się, że żądanie jest prawidłowe przed jego przetworzeniem.
retrohacker
2
Jak słodko! To małe stoły Bobby'ego!
Aura
45

Ponieważ oznaczono to pytanie , oto odpowiedź dotycząca tego konkretnego rodzaju ataku:

Jak powiedziano w komentarzach, musisz użyć przygotowanych instrukcji dla każdego zapytania zawierającego dowolne zmienne dane, bez wyjątków .

Bez względu na jakiekolwiek elementy HTML!
Ważne jest, aby zrozumieć, że zapytania SQL muszą być odpowiednio sformatowane, niezależnie od jakichkolwiek czynników zewnętrznych, czy to danych wejściowych HTML, czy czegokolwiek innego.

Chociaż możesz użyć białej listy sugerowanej w innych odpowiedziach w celu sprawdzania poprawności danych wejściowych, nie powinno to wpływać na żadne działania związane z SQL - muszą one pozostać takie same, bez względu na to, czy sprawdziłeś poprawność danych wejściowych HTML, czy nie. Oznacza to, że nadal musisz używać przygotowanych instrukcji podczas dodawania jakichkolwiek zmiennych do zapytania.

Tutaj możesz znaleźć dokładne wyjaśnienie, dlaczego przygotowane oświadczenia są niezbędne i jak prawidłowo z nich korzystać, a gdzie nie mają zastosowania i co zrobić w takim przypadku: The Hitchhiker's Guide to SQL Injection protection

Również to pytanie zostało oznaczone tagiem . Zakładam, że głównie przez przypadek, ale i tak muszę Cię ostrzec, że surowe mysqli nie jest odpowiednim zamiennikiem dla starych funkcji mysq_ * . Po prostu dlatego, że zastosowany w starym stylu nie zapewni żadnego bezpieczeństwa. O ile obsługa przygotowanych instrukcji jest bolesna i kłopotliwa, do tego stopnia, że ​​przeciętny użytkownik PHP po prostu nie jest w stanie ich w ogóle wykonać. Tak więc, jeśli brak ORM lub jakaś biblioteka abstrakcji nie jest opcją, wówczas jedynym wyborem jest PDO .

Twój zdrowy rozsądek
źródło
5
i = losowe (0, 15); // jakieś zapytanie przy użyciu i. Czy nadal muszę tutaj przygotowywać oświadczenie?
Cruncher
2
Po co jest wdrażanie random?
Slicedpan
9
@YourCommonSense zawężone? Dla mnie „Zawsze rób X, a nie będę tego uzasadniał” jest wąskim pojęciem.
Cruncher
5
@Cruncher Nie przychodzi mi do głowy żaden powód, żeby nie zawsze używać przygotowanych wypowiedzi, za każdym razem, do wszystkiego, zawsze. Możesz mi powiedzieć jedno?
Wesley Murch
3
@Cruncher Z którym się zgadzam, wygląda na to, że grasz w Devil's Advocate. Postanowienie zawarte w odpowiedzi również brzmi: „obejmuje dowolne zmienne dane” . Jest kilka przypadków, takich jak rzutowanie int w PHP, ale wydaje się, że lepiej jest użyć po prostu przygotowanych instrukcji. Mówienie (niedoświadczonym) ludziom, że niewielki „wzrost” wydajności jest ważniejszy niż bezpieczeństwo. Odpowiedź brzmi „niekompletna”, ale wysyła mocną wiadomość, którą inni ignorują. Odpocznę moją sprawę.
Wesley Murch
12

Tak.

Każdy może sfałszować wartości, które faktycznie są wysyłane -

WIĘC, aby zweryfikować menu rozwijane, możesz po prostu sprawdzić, aby upewnić się, że wartość, z którą pracujesz, znajduje się na liście rozwijanej - coś takiego byłoby najlepszym (najbardziej zdrowo paranoicznym) sposobem:

if(in_array($_POST['ddMenu'], $dropDownValues){

    $valueYouUseLaterInPDO = $dropDownValues[array_search("two", $arr)];

} else {

    die("effin h4x0rs! Keep off my LAMP!!"); 

}
rm-vanda
źródło
5
Ta odpowiedź jest niekompletna, dodatkowo należy korzystać z przygotowanych zestawień, aby wstrzyknięcie nie było możliwe niezależnie od tego, jaką wartość ustawimy
Slicedpan
7
@Slicedpan Naprawdę? To brzmi tak, jakbyś wskakiwał na modę, nie wiedząc dlaczego ... Jeśli mam kilka możliwych danych wejściowych do zapytania, takich, że mogę sprawdzić, czy wszystkie są w porządku (co wiem, ponieważ je stworzyłem), wtedy nie uzyskasz żadnych dodatkowych korzyści w zakresie bezpieczeństwa, korzystając z przygotowanego wyciągu
Cruncher
3
Tyle tylko, że wymuszenie użycia przygotowanych instrukcji jako konwencji chroni Cię przed wprowadzeniem luk w zabezpieczeniach SQL injection w przyszłości.
Slicedpan
1
@Cruncher Składanie obietnicy „wszystkie wartości na liście rozwijanej zawsze będą bezpieczne” to bardzo niebezpieczna obietnica. Wartości ulegają zmianie, kod niekoniecznie jest aktualizowany. Osoba aktualizująca wartości może nawet nie wiedzieć, co jest niebezpieczną wartością! Zwłaszcza w dziedzinie programowania internetowego, które jest pełne wszelkiego rodzaju problemów związanych z bezpieczeństwem, skąpanie na czymś takim jest po prostu nieodpowiedzialne (i inne mniej miłe słowa).
hyde
1
@Cruncher Jeśli poprawnie obsługujesz SQL, możesz akceptować dowolne dane wejściowe bez zakłócania prawidłowego działania SQL. Część SQL powinna być przygotowana i gotowa na przyjęcie wszystkiego, bez względu na to, skąd pochodzi. Wszystko inne jest podatne na błędy.
glglgl
8

Jednym ze sposobów ochrony przed użytkownikami zmieniającymi listy rozwijane za pomocą konsoli jest używanie w nich tylko wartości całkowitych. Następnie możesz sprawdzić, czy wartość POST zawiera liczbę całkowitą i w razie potrzeby użyć tablicy, aby przekonwertować ją na tekst. Na przykład:

<?php
// No, you don't need to specify the numbers in the array but as we're using them I always find having them visually there helpful.
$sizes = array(0 => 'All', 1 => 'Large', 2 => 'Medium', 3 => 'Small');
$size = filter_input(INPUT_POST, "size", FILTER_VALIDATE_INT);

echo '<select name="size">';
foreach($sizes as $i => $s) {
    echo '<option value="' . $i . '"' . ($i == $size ? ' selected' : '') . '>' . $s . '</option>';
}
echo '</select>';

Następnie możesz użyć $sizew swoim zapytaniu ze świadomością, że zawsze będzie zawierać FALSEtylko liczbę całkowitą.

Styphon
źródło
2
@OliverBS Co jest, filter_inputjeśli nie jest to sprawdzenie po stronie serwera? Nikt nie może publikować niczego poza liczbą całkowitą.
Styphon
Dzięki Styphon, więc to zastępuje formularz w PO? Czy miałoby to zastosowanie do większych formularzy z wieloma listami rozwijanymi? Jeśli dodałem do niego kolejne menu z napisem „kolor”?
Szmaty
@SamuelTattersfield Tak i tak. Możesz użyć tego dla dowolnej liczby rozwijanych menu, z dowolną liczbą opcji. Po prostu tworzysz nową tablicę dla każdego menu i umieszczasz wszystkie opcje rozwijanego menu w tablicy.
Styphon
Miły! Lubię to. Spróbuję i zobaczę, co dostanę podczas testów.
Szmaty
@SamuelTattersfield Świetnie, po prostu upewnij się, że używasz tej filter_inputczęści również do walidacji, w przeciwnym razie jest tak przydatna jak czajnik czekoladowy dla bezpieczeństwa.
Styphon
7

Pozostałe odpowiedzi już obejmują to, co musisz wiedzieć. Ale może pomoże to wyjaśnić trochę więcej:

dwie rzeczy, które trzeba zrobić:

1. Sprawdź poprawność danych formularza.

Jak jasno pokazuje odpowiedź Jonathana Hobbsa , wybór elementu HTML do danych wejściowych formularza nie zapewnia żadnego niezawodnego filtrowania.

Walidacja jest zwykle przeprowadzana w sposób, który nie zmienia danych, ale pokazuje ponownie formularz, z polami oznaczonymi jako „Popraw to”.

Większość frameworków i systemów CMS ma konstruktory formularzy, które pomagają w tym zadaniu. I nie tylko to, pomagają również przeciwko CSRF (lub „XSRF”), który jest inną formą ataku.

2. Oczyść zmienne / ucieczki w instrukcjach SQL.

.. lub pozwól, aby przygotowane zestawienia zrobiły to za Ciebie.

Jeśli tworzysz (My) instrukcję SQL z dowolnymi zmiennymi, podanymi lub nie przez użytkownika, musisz zmienić znaczenie i zacytować te zmienne.

Ogólnie rzecz biorąc, każda taka zmienna, którą wstawisz do instrukcji MySQL, powinna być albo ciągiem znaków, albo czymś, co PHP może niezawodnie przekształcić w ciąg, który MySQL może przetrawić. Takich jak liczby.

W przypadku łańcuchów musisz wybrać jedną z kilku metod zmiany znaczenia ciągu, co oznacza zastąpienie wszelkich znaków, które miałyby skutki uboczne w MySQL.

  • W starej szkole MySQL + PHP, mysql_real_escape_string () wykonuje zadanie. Problem polega na tym, że zbyt łatwo o tym zapomnieć, dlatego bezwzględnie należy używać przygotowanych instrukcji lub konstruktorów zapytań.
  • W MySQLi możesz korzystać z przygotowanych wyciągów.
  • Większość platform i systemów CMS udostępnia narzędzia do tworzenia zapytań, które pomagają w tym zadaniu.

Jeśli masz do czynienia z liczbą, możesz pominąć ucieczki i cudzysłowy (dlatego przygotowane instrukcje pozwalają określić typ).

Ważne jest, aby zwrócić uwagę, że zmieniasz znaczenie zmiennych dla instrukcji SQL, a NIE dla samej bazy danych . Baza danych będzie przechowywać oryginalny ciąg, ale instrukcja wymaga wersji z ucieczką.

Co się stanie, jeśli pominiesz jedną z nich?

Jeśli nie korzystasz z weryfikacji formularza , ale oczyszczasz dane wejściowe SQL, możesz zobaczyć różne rodzaje złych rzeczy, ale nie zobaczysz iniekcji SQL! (*)

Po pierwsze, może spowodować, że Twoja aplikacja wejdzie w stan, którego nie planowałeś. Np. Jeśli chcesz obliczyć średni wiek wszystkich użytkowników, ale jeden użytkownik podał „aljkdfaqer” jako wiek, obliczenia nie powiodą się.

Po drugie, może istnieć wiele innych ataków typu injection, które należy wziąć pod uwagę: np. Dane wejściowe użytkownika mogą zawierać kod JavaScript lub inne elementy.

Nadal mogą występować problemy z bazą danych: np. Jeśli pole (kolumna tabeli bazy danych) jest ograniczone do 255 znaków, a łańcuch jest dłuższy. Lub jeśli pole akceptuje tylko liczby, a zamiast tego spróbujesz zapisać ciąg nienumeryczny. Ale to nie jest „wstrzyknięcie”, to po prostu „zawieszenie aplikacji”.

Ale nawet jeśli masz wolne pole tekstowe, w którym zezwalasz na dowolne dane wejściowe bez żadnej walidacji, nadal możesz zapisać to w bazie danych po prostu w ten sposób, jeśli odpowiednio zmienisz jego znaczenie, gdy przechodzi do instrukcji bazy danych. Problem pojawia się, gdy chcesz gdzieś użyć tego ciągu.

(*) albo byłoby to coś naprawdę egzotycznego.

Jeśli nie zmienisz znaczenia zmiennych dla instrukcji SQL , ale sprawdziłeś poprawność danych wejściowych formularza, nadal możesz zobaczyć, że dzieje się źle.

Po pierwsze, ryzykujesz, że kiedy zapiszesz dane do bazy danych i załadujesz je ponownie, nie będą to już te same dane, „zagubione w tłumaczeniu”.

Po drugie, może spowodować nieprawidłowe instrukcje SQL, a tym samym spowodować awarię aplikacji. Np. Jeśli jakakolwiek zmienna zawiera cudzysłów lub znak podwójnego cudzysłowu, w zależności od tego, jakiego typu cudzysłowu używasz, otrzymasz nieprawidłowe wyrażenie MySQL.

Po trzecie, nadal może powodować wstrzykiwanie SQL.

Jeśli dane wejściowe użytkownika z formularzy są już przefiltrowane / zatwierdzone, celowe wstrzyknięcie SQl może być mniej prawdopodobne, JEŚLI dane wejściowe zostaną zredukowane do zakodowanej na stałe listy opcji lub jeśli są ograniczone do liczb. Ale do wstrzyknięcia SQL można użyć dowolnego wpisanego tekstu, jeśli nie zmienisz odpowiednio zmiennych w instrukcjach SQL.

I nawet jeśli w ogóle nie masz danych wejściowych do formularza, nadal możesz mieć ciągi znaków z różnych źródeł: odczytywane z systemu plików, usuwane z Internetu itp. Nikt nie może zagwarantować, że te ciągi są bezpieczne.

Don Kichot
źródło
Ani zbyt długie dane, ani ciąg w polu numerycznym nie spowodują awarii
Twój zdrowy rozsądek
hmm, właśnie próbowałem tego teraz i rzeczywiście wyświetla ostrzeżenie zamiast błędu. Myślę, że to PDO zamienia to w błąd. Jestem pewien, że omówiłem w kilkunastu wydaniach na drupal.org, gdzie jakiś ciąg znaków jest za długi na varchar.
donquixote
6

Twoja przeglądarka internetowa nie „wie”, że otrzymuje stronę z php, widzi jedynie html. A warstwa http wie jeszcze mniej. Musisz być w stanie obsłużyć prawie każdy rodzaj danych wejściowych, które mogą przekroczyć warstwę http (na szczęście dla większości wejściowych php już wystąpi błąd). Jeśli próbujesz zapobiec zepsuciu bazy danych przez złośliwe żądania, musisz założyć, że facet po drugiej stronie wie, co robi, i nie ogranicza się do tego, co możesz zobaczyć w przeglądarce w normalnych okolicznościach ( nie wspominając o tym, co możesz majstrować przy narzędziach programistycznych przeglądarki). Więc tak, musisz uwzględnić wszelkie dane wejściowe z menu rozwijanego, ale w przypadku większości danych wejściowych możesz podać błąd.

Bezimienny
źródło
6

Fakt, że ograniczyłeś użytkownika do używania tylko wartości z określonej listy rozwijanej, nie ma znaczenia. Użytkownik techniczny może przechwycić żądanie http wysłane do serwera, zanim opuści jego sieć, zmienić je za pomocą narzędzia takiego jak lokalny serwer proxy, a następnie kontynuować w drodze. Korzystając ze zmienionego żądania, mogą wysyłać wartości parametrów, które nie są tymi, które zostały określone na liście rozwijanej. Deweloperzy muszą mieć nastawienie, że ograniczenia klienta są często bez znaczenia, ponieważ wszystko na kliencie można zmienić. Sprawdzanie poprawności serwera jest wymagane w każdym punkcie wprowadzania danych klienta. Atakujący polegają na naiwności programistów tylko w tym aspekcie.

Jason Higgins
źródło
5

Najlepiej jest użyć sparametryzowanego zapytania, aby uniknąć iniekcji SQL. W takim przypadku zapytanie wyglądałoby tak:

SELECT * FROM table WHERE size = ?

Gdy zapytanie, takie jak powyżej, zawiera tekst, który nie jest zweryfikowany pod kątem integralności (dane wejściowe nie są sprawdzane na serwerze) i zawiera kod iniekcji SQL, zostanie on poprawnie obsłużony. Innymi słowy, żądanie spowoduje coś takiego, jak to się dzieje w warstwie bazy danych:

SELECT * FROM table WHERE size = 'DROP table;'

Spowoduje to po prostu wybranie 0 wyników, gdy zwróci, co spowoduje, że zapytanie będzie nieskuteczne, powodując szkody w bazie danych bez potrzeby stosowania białej listy, kontroli weryfikacyjnej lub innych technik. Należy pamiętać, że odpowiedzialny programista zapewni bezpieczeństwo w warstwach i często oprócz parametryzacji zapytań będzie sprawdzać poprawność. Jednak jest bardzo mało powodów, aby nie parametryzować zapytań z punktu widzenia wydajności, a zabezpieczenia dodane przez tę praktykę są dobrym powodem, aby zapoznać się z zapytaniami parametrycznymi.

Dan Smith
źródło
4

Cokolwiek zostanie przesłane z formularza, trafi na serwer jako tekst przez przewody. Nic nie powstrzyma nikogo przed stworzeniem bota, który będzie naśladował klienta lub wpisał go z terminala, jeśli tego chce. Nigdy nie zakładaj, że skoro zaprogramowałeś klienta, będzie on działał tak, jak myślisz. Naprawdę łatwo to sfałszować.

Przykład tego, co może i co się stanie, gdy zaufasz klientowi.

GenericJam
źródło
4

Haker może całkowicie ominąć przeglądarkę, w tym sprawdzanie formularzy JavaScript, wysyłając żądanie za pomocą usługi Telnet. Oczywiście spojrzy na kod Twojej strony html, aby uzyskać nazwy pól, których musi użyć, ale od tego momentu wszystko mu idzie. Dlatego musisz sprawdzić wszystkie wartości przesłane na serwerze, tak jakby nie pochodziły ze strony html.

user1452686
źródło