Czy htmlspecialchars i mysql_real_escape_string chronią mój kod PHP przed wstrzyknięciem?

Odpowiedzi:

241

Jeśli chodzi o zapytania do bazy danych, zawsze staraj się używać przygotowanych zapytań parametrycznych. mysqliI PDObiblioteki obsługują. Jest to nieskończenie bezpieczniejsze niż używanie funkcji ucieczki, takich jak mysql_real_escape_string.

Tak, w mysql_real_escape_stringrzeczywistości jest to po prostu funkcja ucieczki ciągu. To nie jest magiczna kula. Wszystko, co zrobi, to unikanie niebezpiecznych znaków, aby można było ich bezpiecznie używać w pojedynczym ciągu zapytania. Jeśli jednak nie wyczyścisz wcześniej swoich danych wejściowych, będziesz podatny na niektóre wektory ataku.

Wyobraź sobie następujący SQL:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

Powinieneś być w stanie zobaczyć, że jest to podatne na wykorzystanie.
Wyobraź sobie, że idparametr zawiera wspólny wektor ataku:

1 OR 1=1

Nie ma tam żadnych ryzykownych znaków do zakodowania, więc przejdą one prosto przez uciekający filtr. Opuszczając nas:

SELECT fields FROM table WHERE id= 1 OR 1=1

Co jest pięknym wektorem iniekcji SQL i pozwoliłoby atakującemu zwrócić wszystkie wiersze. Lub

1 or is_admin=1 order by id limit 1

który produkuje

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

Dzięki temu osoba atakująca może zwrócić dane pierwszego administratora w tym całkowicie fikcyjnym przykładzie.

Chociaż te funkcje są przydatne, należy ich używać ostrożnie. Musisz upewnić się, że wszystkie dane wejściowe z sieci są do pewnego stopnia zweryfikowane. W tym przypadku widzimy, że możemy zostać wykorzystani, ponieważ nie sprawdziliśmy, czy zmienna, której używaliśmy jako liczba, była w rzeczywistości numeryczna. W PHP powinieneś szeroko używać zestawu funkcji, aby sprawdzić, czy dane wejściowe są liczbami całkowitymi, zmiennoprzecinkowymi, alfanumerycznymi itp. Ale jeśli chodzi o SQL, zwracaj szczególną uwagę na wartość przygotowanej instrukcji. Powyższy kod byłby bezpieczny, gdyby był przygotowaną instrukcją, ponieważ funkcje bazy danych wiedziałyby, że 1 OR 1=1nie jest to poprawny literał.

Co do htmlspecialchars(). To samo w sobie pole minowe.

W PHP jest prawdziwy problem polegający na tym, że ma cały wybór różnych funkcji ucieczki związanych z HTML-em i nie ma jasnych wskazówek, które dokładnie funkcje robią.

Po pierwsze, jeśli jesteś wewnątrz tagu HTML, masz poważne kłopoty. Patrzeć na

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

Jesteśmy już w tagu HTML, więc nie musimy <lub> robić nic niebezpiecznego. Nasz wektor ataku mógłby być po prostujavascript:alert(document.cookie)

Teraz wynikowy HTML wygląda tak

<img src= "javascript:alert(document.cookie)" />

Atak przebiega prosto.

Pogarsza się. Czemu? ponieważ htmlspecialchars(nazywane w ten sposób) koduje tylko podwójne cudzysłowy, a nie pojedyncze. Więc gdybyśmy mieli

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

Nasz zły napastnik może teraz wprowadzić zupełnie nowe parametry

pic.png' onclick='location.href=xxx' onmouseover='...

daje nam

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

W takich przypadkach nie ma magicznej kuli, wystarczy, że sam wymyślisz dane wejściowe. Jeśli spróbujesz odfiltrować złe postacie, z pewnością poniesiesz porażkę. Podejdź do białej listy i przepuszczaj tylko te znaki, które są dobre. Spójrz na ściągawkę XSS, aby zobaczyć przykłady tego, jak różnorodne mogą być wektory

Nawet jeśli używasz htmlspecialchars($string)poza tagami HTML, nadal jesteś podatny na ataki z użyciem wielobajtowych zestawów znaków.

Najskuteczniejsze jest użycie kombinacji mb_convert_encoding i htmlentities w następujący sposób.

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

Nawet to sprawia, że ​​IE6 jest podatny na ataki ze względu na sposób, w jaki obsługuje UTF. Możesz jednak powrócić do bardziej ograniczonego kodowania, takiego jak ISO-8859-1, dopóki nie spadnie użycie IE6.

Aby uzyskać bardziej szczegółowe badanie problemów wielobajtowych, zobacz https://stackoverflow.com/a/12118602/1820

Cheekysoft
źródło
24
Jedyną rzeczą pominiętą w tym miejscu jest to, że pierwszy przykład zapytania DB ... prosta intval () rozwiązałaby wstrzyknięcie. Zawsze używaj intval () zamiast mysqlescape ... (), gdy potrzebujesz liczby, a nie ciągu.
Robert K
11
i pamiętaj, że używanie sparametryzowanych zapytań pozwoli Ci zawsze traktować dane jako dane, a nie kod. Korzystaj z biblioteki, takiej jak PDO, i używaj zapytań parametrycznych, gdy tylko jest to możliwe.
Cheekysoft,
9
Dwie uwagi: 1. W pierwszym przykładzie byłoby bezpieczne, gdybyśmy umieścili również cudzysłowy wokół parametru, np. $result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2. W drugim przypadku (atrybut zawierający adres URL) nie ma sensu htmlspecialcharsw ogóle; w takich przypadkach należy zakodować dane wejściowe przy użyciu schematu kodowania adresu URL, np rawurlencode. za pomocą . W ten sposób użytkownik nie może wstawić javascript:et al.
Marcel Korpel,
7
„Htmlspecialchars koduje tylko podwójne cudzysłowy, a nie pojedyncze”: to nieprawda, zależy to od ustawionych flag, zobacz jego parametry .
Marcel Korpel
2
Należy to pogrubić: Czarna Take a whitelist approach and only let through the chars which are good.lista zawsze coś pominie. +1
Jo Smo
10

Oprócz doskonałej odpowiedzi Cheekysoft:

  • Tak, zapewnią Ci bezpieczeństwo, ale tylko wtedy, gdy będą używane absolutnie poprawnie. Używaj ich nieprawidłowo, a nadal będziesz podatny na ataki i możesz mieć inne problemy (na przykład uszkodzenie danych)
  • Zamiast tego użyj zapytań sparametryzowanych (jak podano powyżej). Możesz ich używać np. Przez PDO lub poprzez opakowanie takie jak PEAR DB
  • Upewnij się, że magic_quotes_gpc i magic_quotes_runtime są wyłączone przez cały czas i nigdy nie włączają się przypadkowo, nawet na krótko. Są to wczesne i głęboko błędne próby programistów PHP mające na celu zapobieżenie problemom z bezpieczeństwem (które niszczą dane)

Naprawdę nie ma srebrnej kuli do zapobiegania wstrzykiwaniu HTML (np. Cross-site scripting), ale możesz to osiągnąć łatwiej, jeśli używasz biblioteki lub systemu szablonów do tworzenia HTML. Przeczytaj dokumentację, aby dowiedzieć się, jak odpowiednio uciec od rzeczy.

W HTML rzeczy muszą być chronione inaczej w zależności od kontekstu. Jest to szczególnie prawdziwe w przypadku ciągów umieszczanych w JavaScript.

MarkR
źródło
3

Zdecydowanie zgodziłbym się z powyższymi postami, ale mam jedną małą rzecz do dodania w odpowiedzi na odpowiedź Cheekysoftu, a konkretnie:

Jeśli chodzi o zapytania do bazy danych, zawsze staraj się używać przygotowanych zapytań parametrycznych. Obsługują to biblioteki mysqli i PDO. Jest to nieskończenie bezpieczniejsze niż używanie funkcji ucieczki, takich jak mysql_real_escape_string.

Tak, mysql_real_escape_string jest właściwie tylko funkcją uciekającą przed ciągiem znaków. To nie jest magiczna kula. Wszystko, co zrobi, to unikanie niebezpiecznych znaków, aby można było ich bezpiecznie używać w pojedynczym ciągu zapytania. Jeśli jednak nie wyczyścisz wcześniej danych wejściowych, będziesz podatny na określone wektory ataku.

Wyobraź sobie następujący SQL:

$ wynik = "WYBIERZ pola z tabeli WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);

Powinieneś być w stanie zobaczyć, że jest to podatne na wykorzystanie. Wyobraź sobie, że parametr id zawiera wspólny wektor ataku:

1 LUB 1 = 1

Nie ma tam żadnych ryzykownych znaków do zakodowania, więc przejdą one prosto przez uciekający filtr. Opuszczając nas:

WYBIERZ pola z tabeli WHERE id = 1 LUB 1 = 1

Zakodowałem małą, szybką funkcję, którą umieściłem w mojej klasie bazy danych, która usunie wszystko, co nie jest liczbą. Używa preg_replace, więc prawdopodobnie jest nieco bardziej zoptymalizowana funkcja, ale działa w mgnieniu oka ...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

Więc zamiast używać

$ wynik = "WYBIERZ pola z tabeli WHERE id =" .mysqlrealescapestring ("1 LUB 1 = 1");

użyłbym

$ wynik = "WYBIERZ pola z tabeli WHERE id =". Liczby ("1 LUB 1 = 1");

i bezpiecznie uruchomi zapytanie

WYBIERZ pola z tabeli WHERE id = 111

Jasne, to właśnie powstrzymało wyświetlanie poprawnego wiersza, ale nie sądzę, żeby to był duży problem dla każdego, kto próbuje wstrzyknąć sql do Twojej witryny;)

BrilliantWinter
źródło
1
Idealny! To jest dokładnie taki rodzaj odkażania, jakiego potrzebujesz. Początkowy kod nie powiódł się, ponieważ nie sprawdził, czy liczba jest numeryczna. Twój kod to robi. powinieneś wywołać Numbers () na wszystkich zmiennych wykorzystujących liczby całkowite, których wartości pochodzą spoza bazy kodu.
Cheekysoft,
1
Warto wspomnieć, że funkcja intval () będzie działała w tym przypadku doskonale, ponieważ PHP automatycznie zamienia liczby całkowite na łańcuchy.
Adam Ernst,
11
Wolę intval. Zmienia 1abc2 na 1, a nie 12.
jmucchiello
1
intval jest lepsze, szczególnie na ID. W większości przypadków, jeśli jest uszkodzony, jest taki sam, jak powyżej, 1 lub 1 = 1. Naprawdę nie powinieneś ujawniać cudzych identyfikatorów. Więc intval zwróci poprawny identyfikator. Następnie należy sprawdzić, czy oryginalne i wyczyszczone wartości są takie same. To świetny sposób nie tylko na zatrzymanie ataków, ale także na znajdowanie napastników.
triunenatura
2
Niepoprawny wiersz byłby katastrofalny, jeśli pokazujesz dane osobowe, zobaczysz informacje innego użytkownika! zamiast tego lepiej by było sprawdzićreturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Frank Forte
2

Ważnym elementem tej układanki są konteksty. Ktoś wysyłający jako identyfikator „1 OR 1 = 1” nie stanowi problemu, jeśli zacytujesz każdy argument w zapytaniu:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

Co skutkuje w:

SELECT fields FROM table WHERE id='1 OR 1=1'

co jest nieskuteczne. Ponieważ unikasz ciągu, dane wejściowe nie mogą wyrwać się z kontekstu ciągu. Przetestowałem to do wersji 5.0.45 MySQL i użycie kontekstu łańcuchowego dla kolumny z liczbami całkowitymi nie powoduje żadnych problemów.

Lucas Oman
źródło
15
a następnie zacznę mój wektor ataku od wielobajtowego znaku 0xbf27, który w Twojej bazie danych latin1 zostanie przekonwertowany przez funkcję filtru na 0xbf5c27 - czyli pojedynczy znak wielobajtowy, po którym następuje pojedynczy cudzysłów.
Cheekysoft
8
Staraj się nie chronić przed jednym znanym wektorem ataku. Skończysz gonić za swoim ogonem do końca stosowania łatki za poprawką do kodu. Odsuwanie się i przyglądanie się ogólnym przypadkom pozwoli na bezpieczniejszy kod i lepsze nastawienie na bezpieczeństwo.
Cheekysoft,
Zgadzam się; w idealnej sytuacji OP wykorzysta przygotowane zestawienia.
Lucas Oman
1
Chociaż cytowanie argumentów sugerowanych w tym poście nie jest niezawodne, złagodzi wiele typowych ataków typu 1 LUB 1 = 1, więc warto o tym wspomnieć.
Night Owl
2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

Działa dobrze, nawet lepiej w systemach 64-bitowych. Uważaj jednak na ograniczenia systemu w adresowaniu dużych liczb, ale w przypadku identyfikatorów baz danych działa to świetnie w 99% przypadków.

Powinieneś także używać jednej funkcji / metody do czyszczenia wartości. Nawet jeśli ta funkcja jest tylko opakowaniem dla mysql_real_escape_string (). Czemu? Ponieważ pewnego dnia, gdy zostanie znaleziony exploit do preferowanej metody czyszczenia danych, wystarczy zaktualizować go tylko w jednym miejscu, a nie wyszukiwać i zamieniać w całym systemie.

cnizzardini
źródło
-3

dlaczego, och, DLACZEGO, nie umieszczasz cudzysłowów wokół danych wejściowych użytkownika w instrukcji sql? wydaje się całkiem głupie, żeby tego nie robić! dodanie cudzysłowów do instrukcji sql uczyniłoby "1 lub 1 = 1" bezowocną próbą, nie?

więc teraz powiesz: „a co, jeśli użytkownik umieści cudzysłów (lub podwójne cudzysłowy) w danych wejściowych?”

cóż, łatwe rozwiązanie: po prostu usuń cudzysłowy wprowadzone przez użytkownika. np input =~ s/'//g;. : . teraz wydaje mi się, że dane wejściowe użytkownika byłyby zabezpieczone ...

Jarett L
źródło
„Dlaczego, och DLACZEGO, nie umieszczałbyś cudzysłowów wokół danych wejściowych użytkownika w instrukcji sql?” - Pytanie nie mówi nic o nie cytowaniu danych wprowadzonych przez użytkownika.
Quentin
1
„cóż, łatwa naprawa” - straszna poprawka. To odrzuca dane. Rozwiązanie wspomniane w samym pytaniu jest lepszym podejściem.
Quentin
chociaż zgadzam się, że pytanie nie dotyczy cytowania danych wejściowych użytkownika, nadal wydaje się, że nie należy cytować danych wejściowych. i wolałbym raczej podrzucić dane niż wprowadzać złe dane. generalnie w ataku iniekcyjnym i tak NIE chcesz tych danych ... prawda?
Jarett L
„chociaż zgadzam się, że pytanie nie dotyczy cytowania danych wejściowych użytkownika, nadal wydaje się, że nie należy cytować danych wejściowych”. - Nie, nie działa. Pytanie nie demonstruje tego w taki czy inny sposób.
Quentin
1
@JarettL Albo przyzwyczaić się do korzystania z przygotowanych wyciągów, albo przyzwyczaić się do tabel Bobby'ego niszczących dane w każdy wtorek . Sparametryzowany SQL to najlepszy sposób ochrony przed iniekcją SQL. Nie musisz wykonywać „sprawdzania wstrzyknięć SQL”, jeśli używasz przygotowanej instrukcji. Są niezwykle łatwe w implementacji (i moim zdaniem sprawiają, że kod jest DUŻO łatwiejszy do odczytania), chronią przed różnymi idiosynkrazjami związanymi z konkatenacją ciągów i wtryskiem sql, a co najważniejsze, nie musisz wymyślać koła na nowo, aby go zaimplementować .
Siyual