Mysql
 sql >> база данни >  >> RDS >> Mysql

SQL инжекция, която заобикаля mysql_real_escape_string()

Краткият отговор е да, да, има начин да заобиколите mysql_real_escape_string() .#За много НЕЯСНИ СЛУЧАИ!!!

Дългият отговор не е толкова лесен. Базира се на атака, демонстрирана тук .

Атаката

И така, нека започнем с показване на атаката...

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");

При определени обстоятелства това ще върне повече от 1 ред. Нека да анализираме какво се случва тук:

  1. Избор на набор от знаци

    mysql_query('SET NAMES gbk');
    

    За да работи тази атака, се нуждаем от кодирането, което сървърът очаква от връзката, за да кодира ' както в ASCII, т.е. 0x27 и да има някакъв символ, чийто последен байт е ASCII \ т.е. 0x5c . Както се оказва, има 5 такива кодировки, поддържани в MySQL 5.6 по подразбиране:big5 , cp932 , gb2312 , gbk и sjis . Ще изберем gbk тук.

    Сега е много важно да се отбележи използването на SET NAMES тук. Това задава набора от знаци НА СЪРВЪРА . Ако използвахме извикването на функцията на C API mysql_set_charset() , ще се оправим (при версиите на MySQL от 2006 г.). Но повече за това защо след минута...

  2. Полезният товар

    Полезният товар, който ще използваме за това инжектиране, започва с байтовата последователност 0xbf27 . В gbk , това е невалиден многобайтов знак; на latin1 , това е низът ¿' . Имайте предвид, че на latin1 и gbk , 0x27 сам по себе си е литерал ' знак.

    Избрахме този полезен товар, защото ако извикахме addslashes() върху него бихме вмъкнали ASCII \ т.е. 0x5c , преди ' характер. Така че ще завършим с 0xbf5c27 , което в gbk е последователност от два знака:0xbf5c последвано от 0x27 . Или с други думи, валиден знак, последван от неекраниран ' . Но ние не използваме addslashes() . И така, към следващата стъпка...

  3. mysql_real_escape_string()

    Извикването на C API към mysql_real_escape_string() се различава от addslashes() тъй като познава набора от символи за връзка. Така че може да изпълни екранирането правилно за набора от знаци, който сървърът очаква. Въпреки това, до този момент клиентът смята, че все още използваме latin1 за връзката, защото никога не сме го казвали другояче. Казахме на сървъра ние използваме gbk , но клиентът все още смята, че е latin1 .

    Следователно извикването на mysql_real_escape_string() вмъква обратната наклонена черта и имаме свободно висящ ' герой в нашето "избягало" съдържание! Всъщност, ако погледнем $var в gbk набор от символи, ще видим:

    縗' OR 1=1 /*

    Кое е какво точно атаката изисква.

  4. Запитването

    Тази част е само формалност, но ето изобразената заявка:

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

Поздравления, току-що успешно атакувахте програма с помощта на mysql_real_escape_string() ...

Лошото

Влошава се. PDO по подразбиране имитира подготвени изявления с MySQL. Това означава, че от страна на клиента той основно прави sprintf чрез mysql_real_escape_string() (в библиотеката C), което означава, че следното ще доведе до успешно инжектиране:

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

Сега си струва да се отбележи, че можете да предотвратите това, като деактивирате емулирани подготвени изрази:

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

Това ще обикновено води до вярно подготвено изявление (т.е. данните се изпращат в отделен пакет от заявката). Въпреки това, имайте предвид, че PDO тихо отменя за емулиране на изрази, които MySQL не може да подготви първоначално:тези, които може, са изброени в ръководството, но внимавайте да изберете подходящата версия на сървъра).

Грозният

В самото начало казах, че бихме могли да предотвратим всичко това, ако бяхме използвали mysql_set_charset('gbk') вместо SET NAMES gbk . И това е вярно, при условие че използвате версия на MySQL от 2006 г.

Ако използвате по-ранна версия на MySQL, тогава бъг в mysql_real_escape_string() означаваше, че невалидни многобайтови знаци като тези в нашия полезен товар се третират като единични байтове за избягване на целите дори клиентът да е бил правилно информиран за кодирането на връзката и така тази атака все пак щеше да успее. Грешката беше коригирана в MySQL 4.1.20 , 5.0.22 и 5.1.11 .

Но най-лошото е, че PDO не изложи C API за mysql_set_charset() до 5.3.6, така че в предишни версии не може предотвратявайте тази атака за всяка възможна команда! Вече е изложена като DSN параметър .

Благодатта за спасяване

Както казахме в началото, за да работи тази атака, връзката към базата данни трябва да бъде кодирана с помощта на уязвим набор от знаци. utf8mb4 не е уязвим и въпреки това може да поддържа всеки Unicode символ:така че можете да изберете да го използвате вместо това — но той е достъпен само от MySQL 5.5.3. Алтернатива е utf8 , което също не е уязвимо и може да поддържа целия Unicode Основна многоезична равнина .

Като алтернатива можете да активирате NO_BACKSLASH_ESCAPES SQL режим, който (наред с други неща) променя работата на mysql_real_escape_string() . С активиран този режим, 0x27 ще бъде заменен с 0x2727 вместо 0x5c27 и по този начин процесът на избягване не може създайте валидни знаци във всяка от уязвимите кодировки, където те не са съществували преди (т.е. 0xbf27 все още е 0xbf27 и т.н.) – така че сървърът все пак ще отхвърли низа като невалиден. Все пак вижте отговора на @eggyal за различна уязвимост, която може да възникне при използването на този SQL режим.

Безопасни примери

Следните примери са безопасни:

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");

Тъй като сървърът очаква 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");

Защото сме задали правилно набора от знаци, така че клиентът и сървърът да съвпадат.

$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 /*"));

Защото сме изключили емулирани подготвени оператори.

$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 /*"));

Защото сме задали правилно набора от знаци.

$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();

Защото MySQLi прави верни подготвени изявления през цялото време.

Приключване

Ако вие:

  • Използвайте съвременни версии на MySQL (късна версия 5.1, всички 5.5, 5.6 и т.н.) И mysql_set_charset() / $mysqli->set_charset() / Параметър на DSN на PDO (в PHP ≥ 5.3.6)

ИЛИ

  • Не използвайте уязвим набор от знаци за кодиране на връзката (използвате само utf8 / latin1 / ascii / и т.н.)

Вие сте 100% в безопасност.

В противен случай сте уязвими въпреки че използвате 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. Топ 50 въпроса за интервю за MySQL, които трябва да подготвите през 2022 г

  2. MySQL репликация и отказване, базирано на GTID - дълбоко потапяне в грешни транзакции

  3. отказан достъп за потребител @ 'localhost' до база данни ''

  4. MySQL Errno 150

  5. Променете MySQL колона, за да бъде AUTO_INCREMENT