Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Wstrzyknięcie SQL, które omija mysql_real_escape_string()

Krótka odpowiedź to tak, tak, jest sposób na obejście mysql_real_escape_string() .#Dla bardzo NIEZNANYCH PRZYPADKÓW!!!

Długa odpowiedź nie jest taka prosta. Opiera się na ataku pokazany tutaj .

Atak

Zacznijmy więc od pokazania ataku...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

W pewnych okolicznościach zwróci więcej niż 1 wiersz. Przeanalizujmy, co się tutaj dzieje:

  1. Wybieranie zestawu znaków

    mysql_query('SET NAMES gbk');
    

    Aby ten atak zadziałał, potrzebujemy kodowania, którego oczekuje serwer w połączeniu, aby zakodować ' jak w ASCII tj. 0x27 i mieć jakiś znak, którego końcowy bajt to ASCII \ tj. 0x5c . Jak się okazuje, domyślnie w MySQL 5.6 obsługiwanych jest 5 takich kodowań:big5 , cp932 , gb2312 , gbk i sjis . Wybierzemy gbk tutaj.

    Teraz bardzo ważne jest, aby zwrócić uwagę na użycie SET NAMES tutaj. To ustawia zestaw znaków NA SERWERZE . Jeśli użyliśmy wywołania funkcji C API mysql_set_charset() , wszystko byłoby dobrze (w wydaniach MySQL od 2006). Ale więcej o tym za chwilę...

  2. Ładunek

    Ładunek, którego zamierzamy użyć do tego wstrzyknięcia, zaczyna się od sekwencji bajtów 0xbf27 . W gbk , to jest nieprawidłowy znak wielobajtowy; w języku latin1 , jest to ciąg ¿' . Zauważ, że w latin1 i gbk , 0x27 sam w sobie jest dosłownym ' znak.

    Wybraliśmy ten ładunek, ponieważ jeśli wywołaliśmy addslashes() na nim wstawilibyśmy ASCII \ tj. 0x5c , przed ' postać. Więc skończylibyśmy z 0xbf5c27 , który w gbk jest sekwencją dwóch znaków:0xbf5c po którym następuje 0x27 . Innymi słowy, ważny znak, po którym następuje nieunikniony ' . Ale nie używamy addslashes() . Przejdźmy do następnego kroku...

  3. mysql_real_escape_string()

    Wywołanie C API do mysql_real_escape_string() różni się od addslashes() w tym, że zna zestaw znaków połączenia. Dzięki temu może poprawnie wykonać ucieczkę dla zestawu znaków, którego oczekuje serwer. Jednak do tego momentu klient myśli, że nadal używamy latin1 za połączenie, ponieważ nigdy nie powiedzieliśmy inaczej. Poinformowaliśmy serwer używamy gbk , ale klient nadal myśli, że to latin1 .

    Dlatego wywołanie mysql_real_escape_string() wstawia odwrotny ukośnik i mamy swobodny wiszący ' postaci w naszej "uciekłej" zawartości! W rzeczywistości, gdybyśmy spojrzeli na $var w gbk zestaw znaków, zobaczymy:

    縗' OR 1=1 /*

    Czyli dokładnie co atak wymaga.

  4. Zapytanie

    Ta część to tylko formalność, ale oto renderowane zapytanie:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

Gratulacje, właśnie pomyślnie zaatakowałeś program przy użyciu mysql_real_escape_string() ...

Zły

Pogarsza się. PDO domyślnie emulacja przygotowane zestawienia z MySQL. Oznacza to, że po stronie klienta zasadniczo wykonuje sprintf przez mysql_real_escape_string() (w bibliotece C), co oznacza pomyślne wstrzyknięcie:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Teraz warto zauważyć, że możesz temu zapobiec, wyłączając emulowane przygotowane instrukcje:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

To zazwyczaj skutkuje prawdziwym przygotowanym oświadczeniem (tj. danymi przesyłanymi w oddzielnym pakiecie od zapytania). Należy jednak pamiętać, że PDO po cichu powrót do emulowania instrukcji, których MySQL nie może przygotować natywnie:te, których potrafi to wymienione w instrukcji, ale uważaj, aby wybrać odpowiednią wersję serwera).

Brzydkie

Powiedziałem na samym początku, że moglibyśmy temu zapobiec, gdybyśmy użyli mysql_set_charset('gbk') zamiast SET NAMES gbk . I to prawda, pod warunkiem, że używasz wersji MySQL od 2006 roku.

Jeśli używasz wcześniejszej wersji MySQL, to błąd w mysql_real_escape_string() oznaczało, że nieprawidłowe znaki wielobajtowe, takie jak te w naszym ładunku, były traktowane jako pojedyncze bajty dla celów ucieczki nawet jeśli klient został prawidłowo poinformowany o kodowaniu połączenia i tak ten atak się powiódł. Błąd został naprawiony w MySQL 4.1.20 , 5.0.22 i 5.1.11 .

Ale najgorsze jest to, że PDO nie ujawnił C API dla mysql_set_charset() do 5.3.6, więc w poprzednich wersjach nie zapobiegaj temu atakowi dla każdego możliwego polecenia! Jest teraz ujawniony jako Parametr DSN .

Łaska zbawienia

Jak powiedzieliśmy na początku, aby ten atak zadziałał, połączenie z bazą danych musi być zaszyfrowane przy użyciu zestawu znaków, który jest podatny na ataki. utf8mb4 nie jest zagrożony a mimo to może obsługiwać każdą Znak Unicode:więc możesz użyć go zamiast tego — ale jest dostępny dopiero od MySQL 5.5.3. Alternatywą jest utf8 , który również nie jest zagrożony i może obsługiwać cały Podstawową płaszczyznę wielojęzyczną .

Alternatywnie możesz włączyć NO_BACKSLASH_ESCAPES Tryb SQL, który (między innymi) zmienia działanie mysql_real_escape_string() . Gdy ten tryb jest włączony, 0x27 zostanie zastąpiony przez 0x2727 zamiast 0x5c27 i dlatego proces ucieczki nie może tworzyć prawidłowe znaki w dowolnym z wrażliwych kodowań, w których wcześniej nie istniały (np. 0xbf27 to nadal 0xbf27 itp.) — więc serwer nadal odrzuci ciąg jako nieprawidłowy. Zobacz jednak odpowiedź @eggyal dla innej luki, która może powstać w wyniku używania tego trybu SQL.

Bezpieczne przykłady

Poniższe przykłady są bezpieczne:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Ponieważ serwer oczekuje utf8 ...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Ponieważ właściwie ustawiliśmy zestaw znaków, aby klient i serwer pasowały do ​​siebie.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Ponieważ wyłączyliśmy emulowane przygotowane wyciągi.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Ponieważ poprawnie ustawiliśmy zestaw znaków.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Ponieważ MySQLi cały czas wykonuje prawdziwie przygotowane instrukcje.

Zawijanie

Jeśli:

  • Używaj nowoczesnych wersji MySQL (późne 5.1, wszystkie 5.5, 5.6 itd.) ORAZ mysql_set_charset() / $mysqli->set_charset() / Parametr zestawu znaków DSN PDO (w PHP ≥ 5.3.6)

LUB

  • Nie używaj zagrożonego zestawu znaków do kodowania połączenia (używasz tylko utf8 / latin1 / ascii / itp)

Jesteś w 100% bezpieczny.

W przeciwnym razie jesteś narażony nawet jeśli używasz mysql_real_escape_string() ...



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Co oznacza błąd mysql 1025 (HY000):Błąd zmiany nazwy './foo' (errorno:150)?

  2. Eksport MySQL do pliku outfile:znaki ucieczki CSV

  3. MySQL pomiędzy klauzulą ​​nie włącznie?

  4. Jak mogę przejść przez wszystkie wiersze tabeli? (MySQL)

  5. Wydajność operatora MySQL IN na (dużej?) liczbie wartości