Powiedzmy, że mam taki kod:
$dbh = new PDO("blahblah");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
Dokumentacja ChNP mówi:
Parametry gotowych instrukcji nie muszą być cytowane; kierowca obsługuje to za ciebie.
Czy to naprawdę wszystko, co muszę zrobić, aby uniknąć zastrzyków SQL? Czy to naprawdę takie proste?
Możesz założyć MySQL, jeśli robi to różnicę. Jestem naprawdę ciekawy tylko użycia przygotowanych instrukcji przeciwko iniekcji SQL. W tym kontekście nie dbam o XSS ani inne możliwe luki.
php
security
pdo
sql-injection
Mark Biek
źródło
źródło
Odpowiedzi:
Krótka odpowiedź brzmi NIE , przygotowania PDO nie ochronią cię przed wszystkimi możliwymi atakami SQL-Injection. W przypadku niektórych niejasnych przypadków na krawędziach.
Dostosowuję tę odpowiedź, aby mówić o PDO ...
Długa odpowiedź nie jest taka łatwa. Opiera się na pokazanym tutaj ataku .
Atak
Zacznijmy więc od pokazania ataku ...
W pewnych okolicznościach zwróci więcej niż 1 wiersz. Przeanalizujmy, co się tutaj dzieje:
Wybieranie zestawu znaków
Aby ten atak zadziałał, potrzebujemy kodowania, którego serwer oczekuje w połączeniu zarówno do kodowania
'
jak w ASCII tj.0x27
I do posiadania znaku, którego ostatnim bajtem jest ASCII\
tj0x5c
. Jak się okazuje, istnieje 5 takich kodowania obsługiwane w MySQL 5.6 domyślnie:big5
,cp932
,gb2312
,gbk
isjis
.gbk
Tutaj wybierzemy .Teraz bardzo ważne jest, aby zauważyć użycie
SET NAMES
tutaj. To ustawia zestaw znaków NA SERWERZE . Jest na to inny sposób, ale niedługo się tam dostaniemy.Ładunek
Ładunek, którego użyjemy do tego wstrzyknięcia, zaczyna się od sekwencji bajtów
0xbf27
. Wgbk
to niepoprawny znak wielobajtowy; wlatin1
to jest ciąg¿'
. Zauważ, że wlatin1
igbk
,0x27
na własną rękę jest dosłowny'
charakter.Wybraliśmy ten ładunek, ponieważ jeśli
addslashes()
go wywołamy, wstawimy ASCII,\
tj.0x5c
Przed'
znakiem. Więc chcemy skończyć z0xbf5c27
, któragbk
jest sekwencją dwóch znaków:0xbf5c
następuje0x27
. Lub innymi słowy, prawidłowy znak, po którym następuje nieskalowany'
. Ale my nie używamyaddslashes()
. Przejdźmy do następnego kroku ...$ stmt-> execute ()
Ważne jest, aby zdać sobie sprawę z tego, że PDO domyślnie NIE wykonuje prawdziwie przygotowanych instrukcji. Emuluje je (dla MySQL). Dlatego PDO wewnętrznie buduje ciąg zapytania, wywołując
mysql_real_escape_string()
(funkcja MySQL C API) na każdej wartości powiązanego ciągu.Wywołanie interfejsu API C
mysql_real_escape_string()
różni sięaddslashes()
tym, że zna zestaw znaków połączenia. Dzięki temu może poprawnie wykonać zmianę znaczenia dla zestawu znaków, którego oczekuje serwer. Jednak do tego momentu klient uważa, że nadal używamylatin1
do połączenia, ponieważ nigdy nie powiedzieliśmy tego inaczej. Poinformowaliśmy serwer , którego używamygbk
, ale klient nadal uważa, że taklatin1
.Dlatego wezwanie do
mysql_real_escape_string()
wstawienia odwrotnego ukośnika, a my mamy wolną'
postać w naszej „ucieczce” treści! W rzeczywistości, jeśli byliśmy patrzeć$var
wgbk
zestawie znaków, to widzimy:Właśnie tego wymaga atak.
Zapytanie
Ta część jest tylko formalnością, ale oto renderowane zapytanie:
Gratulacje, właśnie pomyślnie zaatakowałeś program przy użyciu przygotowanych instrukcji PDO ...
Prosta poprawka
Teraz warto zauważyć, że można temu zapobiec, wyłączając emulowane przygotowane instrukcje:
Będzie to zwykle prowadzi do prawdziwej przygotowanego zestawienia (czyli danych wysyłanych przez w oddzielnym pakiecie z zapytania). Należy jednak pamiętać, że PDO będzie cicho awaryjna do emulacji oświadczenia, że MySQL nie może przygotować natywnie: ci, że może są wymienione w instrukcji, ale uważaj, aby wybrać odpowiednią wersję serwera).
Prawidłowa poprawka
Problem polega na tym, że nie nazywają C API
mysql_set_charset()
zamiastSET NAMES
. Gdyby tak było, byłoby nam dobrze, pod warunkiem, że korzystamy z wersji MySQL od 2006 roku.Jeśli używasz wcześniejszej wersji MySQL, potem błąd w
mysql_real_escape_string()
oznaczało, że nieprawidłowe znaki wielobajtowe, takie jak te w naszym ładowności były traktowane jako pojedyncze bajty dla celów ucieczce nawet jeśli klient został prawidłowo poinformowany o kodowaniu połączeń i tak ten atak wciąż się udaje. Błąd został naprawiony w MySQL 4.1.20 , 5.0.22 i 5.1.11 .Ale najgorsze jest to, że
PDO
nie ujawniało C APImysql_set_charset()
do 5.3.6, więc we wcześniejszych wersjach nie mogło zapobiec temu atakowi dla każdego możliwego polecenia! Jest teraz widoczny jako parametr DSN , którego należy użyć zamiastSET NAMES
...The Saving Grace
Jak powiedzieliśmy na wstępie, aby ten atak zadziałał, połączenie z bazą danych musi zostać zakodowane przy użyciu podatnego zestawu znaków. nie
utf8mb4
jest podatny na zagrożenia, a jednak może obsługiwać każdą postać Unicode: możesz więc zamiast tego użyć tej opcji - ale jest ona dostępna tylko od MySQL 5.5.3. Alternatywą jestutf8
, która również nie jest wrażliwa i może obsługiwać cały podstawowy wielojęzyczny samolot Unicode .Alternatywnie możesz włączyć
NO_BACKSLASH_ESCAPES
tryb SQL, który (między innymi) zmienia działaniemysql_real_escape_string()
. Po włączeniu tego trybu0x27
zostanie zastąpione0x2727
zamiast zamiast,0x5c27
dlatego proces zmiany znaczenia nie może utworzyć prawidłowych znaków w żadnym z wrażliwych kodowań, w których wcześniej nie istniały (tj.0xbf27
Nadal jest0xbf27
itp.) - więc serwer nadal odrzuca ciąg jako nieprawidłowy . Jednak zobacz odpowiedź @ eggyal na inną podatność, która może wyniknąć z używania tego trybu SQL (choć nie z PDO).Bezpieczne przykłady
Następujące przykłady są bezpieczne:
Ponieważ serwer oczekuje
utf8
...Ponieważ właściwie ustawiliśmy zestaw znaków, aby klient i serwer pasowały do siebie.
Ponieważ wyłączyliśmy emulowane przygotowane oświadczenia.
Ponieważ poprawnie ustawiliśmy zestaw znaków.
Ponieważ MySQLi cały czas wykonuje prawdziwe przygotowane instrukcje.
Podsumowanie
Jeśli ty:
LUB
utf8
/latin1
/ascii
/ etc)LUB
NO_BACKSLASH_ESCAPES
tryb SQLJesteś w 100% bezpieczny.
W przeciwnym razie jesteś podatny na zagrożenia, nawet jeśli używasz przygotowanych instrukcji PDO ...
Uzupełnienie
Powoli pracuję nad łatką, aby zmienić domyślną opcję, aby nie emulować przygotowań do przyszłej wersji PHP. Problem, na który wpadam, polega na tym, że wiele testów psuje się, kiedy to robię. Jednym z problemów jest to, że emulowane przygotowania przygotują tylko błędy składniowe podczas wykonywania, ale prawdziwe przygotowania przygotują błędy podczas przygotowania. Może to powodować problemy (i jest jednym z powodów, dla których testy się psują).
źródło
mysql_real_escape_string
nie obsługiwał poprawnie przypadków, w których połączenie było poprawnie ustawione na BIG5 / GBK. Tak więc nawet wywołaniemysql_set_charset()
mysql <5.0.22 byłoby podatne na ten błąd! Więc nie, ten post ma nadal zastosowanie do 5.0.22 (ponieważ mysql_real_escape_string jest tylko charset od połączeń, zmysql_set_charset()
których ten post mówi o pomijaniu) ...NO_BACKSLASH_ESCAPES
można również wprowadzić nowe luki w zabezpieczeniach: stackoverflow.com/a/23277864/1014813Przygotowane instrukcje / sparametryzowane zapytania są zasadniczo wystarczające, aby zapobiec wstrzyknięciu pierwszego rzędu do tej instrukcji * . Jeśli użyjesz niezaznaczonej dynamicznej sql gdziekolwiek indziej w twojej aplikacji, nadal będziesz podatny na zastrzyk drugiego rzędu .
Wstrzykiwanie drugiego rzędu oznacza, że dane zostały cyklicznie przeładowane przez bazę danych, zanim zostały uwzględnione w zapytaniu, i jest znacznie trudniejsze do pobrania. AFAIK, prawie nigdy nie widzisz prawdziwych ataków inżynieryjnych drugiego rzędu, ponieważ atakujący zwykle łatwiej im się sprzyja inżynierii społecznej, ale czasami pojawiają się błędy drugiego rzędu z powodu dodatkowych łagodnych
'
postaci lub podobnych.Możesz wykonać atak wstrzykiwania drugiego rzędu, gdy możesz spowodować zapisanie wartości w bazie danych, która później zostanie użyta jako literał w zapytaniu. Jako przykład załóżmy, że podczas tworzenia konta w witrynie internetowej wprowadzasz następujące informacje jako nową nazwę użytkownika (przy założeniu bazy danych MySQL dla tego pytania):
Jeśli nie ma żadnych innych ograniczeń dotyczących nazwy użytkownika, przygotowana instrukcja nadal upewniałaby się, że powyższe osadzone zapytanie nie wykona się w momencie wstawiania i zapisuje wartość poprawnie w bazie danych. Wyobraź sobie jednak, że później aplikacja pobiera nazwę użytkownika z bazy danych i używa konkatenacji ciągów, aby uwzględnić tę wartość w nowym zapytaniu. Możesz zobaczyć hasło kogoś innego. Ponieważ kilka pierwszych nazwisk w tabeli użytkowników to zazwyczaj administratorzy, być może właśnie rozdałeś farmę. (Uwaga: to kolejny powód, dla którego nie należy przechowywać haseł w postaci zwykłego tekstu!)
Widzimy więc, że przygotowane oświadczenia są wystarczające dla pojedynczego zapytania, ale sami są nie wystarczające do ochrony przed sql atakami wstrzyknięcia całej całej aplikacji, ze względu na brak mechanizmu egzekwowania wszystkie dostępu do bazy danych w aplikacji wykorzystuje bezpieczne kod. Jednak używane w ramach dobrego projektowania aplikacji - które mogą obejmować takie praktyki, jak przegląd kodu lub analiza statyczna, lub użycie ORM, warstwy danych lub warstwy usługi, która ogranicza dynamiczne tworzenie kodu SQL - przygotowane instrukcje są podstawowym narzędziem do rozwiązania Sql Injection problem.Jeśli przestrzegasz dobrych zasad projektowania aplikacji, takich jak dostęp do danych jest oddzielony od reszty programu, łatwo jest wymusić lub skontrolować, czy każde zapytanie poprawnie używa parametryzacji. W takim przypadku iniekcja sql (zarówno pierwszego, jak i drugiego rzędu) jest całkowicie uniemożliwiona.
* Okazuje się, że MySql / PHP są (w porządku, były) po prostu głupie w kwestii obsługi parametrów, gdy w grę wchodzą szerokie znaki, i wciąż jest rzadki przypadek opisany w innej wysoko głosowanej odpowiedzi tutaj, która może pozwolić na wstrzyknięcie przez sparametryzowane pytanie.
źródło
Nie, nie zawsze są.
Zależy to od tego, czy zezwalasz na umieszczanie danych wejściowych użytkownika w samym zapytaniu. Na przykład:
byłby podatny na zastrzyki SQL i użycie przygotowanych instrukcji w tym przykładzie nie zadziała, ponieważ dane wejściowe użytkownika są używane jako identyfikator, a nie dane. Właściwą odpowiedzią byłoby tutaj zastosowanie filtrowania / sprawdzania poprawności, takich jak:
Uwaga: nie można używać PDO do wiązania danych wykraczających poza DDL (język definicji danych), tzn. To nie działa:
Powodem, dla którego powyżej nie działa, bo jest
DESC
iASC
nie są dane . PDO może uciec tylko dla danych . Po drugie, nie można nawet umieszczać'
wokół niego cudzysłowów. Jedynym sposobem, aby umożliwić łatwy sortowania jest wybrany filtr ręcznie i sprawdzić, że jest to alboDESC
alboASC
.źródło
SELECT * FROM 'table'
może być niewłaściwy, jak powinien,SELECT * FROM `table`
lub bez żadnych backsticksów. Potem kilka rzeczy, jakORDER BY DESC
, gdzieDESC
pochodzi od użytkownika nie może być po prostu uciekł. Zatem praktyczne scenariusze są raczej nieograniczone.switch
aby uzyskać nazwę tabeli.Tak, wystarczy. Sposób działania ataków typu iniekcyjnego polega na tym, że interpreter (baza danych) w jakiś sposób ocenia coś, co powinno być danymi, jakby to był kod. Jest to możliwe tylko wtedy, gdy łączysz kod i dane na tym samym nośniku (np. Gdy konstruujesz zapytanie jako ciąg znaków).
Zapytania sparametryzowane działają, wysyłając kod i dane osobno, więc nigdy nie byłoby w tym dziury.
Nadal jednak możesz być podatny na ataki typu iniekcyjnego. Na przykład, jeśli użyjesz danych na stronie HTML, możesz podlegać atakom typu XSS.
źródło
Nie, to nie wystarczy (w niektórych szczególnych przypadkach)! Domyślnie PDO używa emulowanych przygotowanych instrukcji, gdy używasz MySQL jako sterownika bazy danych. Zawsze powinieneś wyłączyć emulowane przygotowane instrukcje podczas korzystania z MySQL i PDO:
Kolejną rzeczą, którą należy zawsze zrobić, jest ustawienie prawidłowego kodowania bazy danych:
Zobacz także podobne pytanie: Jak mogę zapobiec wstrzykiwaniu SQL w PHP?
Zauważ również, że dotyczy to tylko strony bazy danych rzeczy, które nadal będziesz musiał oglądać podczas wyświetlania danych. Np. Używając
htmlspecialchars()
ponownie z poprawnym stylem kodowania i cytowania.źródło
Osobiście zawsze najpierw uruchamiałbym jakieś dane sanitarne na danych, ponieważ nigdy nie można ufać danych wejściowych użytkownika, jednak podczas używania symboli zastępczych / wiązania parametrów wprowadzone dane są wysyłane do serwera osobno do instrukcji sql, a następnie łączone razem. Kluczem tutaj jest to, że wiąże to dostarczone dane z określonym typem i konkretnym zastosowaniem i eliminuje możliwość zmiany logiki instrukcji SQL.
źródło
Eaven, jeśli masz zamiar zapobiec front-endowi wstrzykiwania sql, używając czeków HTML lub JS, musisz wziąć pod uwagę, że kontrole frontonu są „pomijalne”.
Możesz wyłączyć js lub edytować wzór za pomocą narzędzia programistycznego front-end (obecnie wbudowanego w Firefoxa lub Chrome).
Tak więc, aby zapobiec wstrzyknięciu SQL, dobrze byłoby zdezynfekować backend daty wejściowej w twoim kontrolerze.
Chciałbym zasugerować, abyś użył natywnej funkcji PHP filter_input () w celu oczyszczenia wartości GET i INPUT.
Jeśli chcesz korzystać z bezpieczeństwa, w przypadku sensownych zapytań do bazy danych, chciałbym zasugerować użycie wyrażenia regularnego do sprawdzania poprawności formatu danych. preg_match () pomoże ci w tym przypadku! Ale uważaj! Silnik Regex nie jest tak lekki. Używaj go tylko w razie potrzeby, w przeciwnym razie wydajność aplikacji spadnie.
Bezpieczeństwo ma swoje koszty, ale nie marnuj swojej wydajności!
Prosty przykład:
jeśli chcesz dokładnie sprawdzić, czy wartość otrzymana z GET jest liczbą, mniejszą niż 99, jeśli (! preg_match ('/ [0-9] {1,2} /')) {...} jest większy od
Zatem ostateczna odpowiedź brzmi: „Nie! Przygotowane oświadczenia PDO nie zapobiegają wszelkiego rodzaju iniekcjom SQL”; Nie zapobiega nieoczekiwanym wartościom, tylko nieoczekiwanej konkatenacji
źródło