Коротка відповідь - так, так, є спосіб обійти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 ряду. Давайте розберемо, що тут відбувається:
Вибір набору символів
mysql_query('SET NAMES gbk');
Щоб ця атака спрацювала, нам потрібно кодування, яке сервер очікує на з'єднання як для кодування, '
так і в ASCII, тобто, 0x27
і мати якийсь символ, кінцевим байтом якого є ASCII, \
тобто 0x5c
. Як з'ясовується, є 5 таких кодувань , підтримуваних в MySQL 5.6 за замовчуванням: big5
, cp932
, gb2312
, gbk
і sjis
. Ми виберемо gbk
тут.
Тепер дуже важливо відзначити використання SET NAMES
тут. Це встановлює набір символів НА СЕРВЕРІ . Якби ми використовували дзвінок до функції API API mysql_set_charset()
, нам було б добре (у релізах MySQL з 2006 року). Але більше про те, чому за хвилину ...
Корисний вантаж
Корисне навантаження, яке ми будемо використовувати для цього введення, починається з послідовності байтів 0xbf27
. В gbk
, це недійсний багатобайтовий символ; в latin1
, це рядок ¿'
. Зверніть увагу , що в latin1
і gbk
, 0x27
само по собі є буквальним '
характер.
Ми вибрали це корисне навантаження, оскільки, якби ми зателефонували addslashes()
на нього, ми вставимо ASCII, \
тобто 0x5c
перед '
символом. Таким чином, ми закінчилися 0xbf5c27
, що gbk
є послідовністю двох символів: 0xbf5c
далі 0x27
. Або іншими словами, дійсний символ, за яким слідує непризначений '
. Але ми не використовуємо addslashes()
. Тож до наступного кроку ...
mysql_real_escape_string ()
Виклик API API mysql_real_escape_string()
відрізняється від того, addslashes()
що він знає набір символів з'єднання. Таким чином, він може виконати втечу належним чином для набору символів, якого очікує сервер. Однак до цього часу клієнт вважає, що ми все ще використовуємо latin1
для з'єднання, оскільки ми ніколи не говорили про це інакше. Ми сказали серверу, який ми використовуємо gbk
, але клієнт все ще вважає, що це latin1
.
Тому заклик mysql_real_escape_string()
вставляти звороту косу рису, і ми маємо вільний висячий '
персонаж у нашому "униклому" вмісті! Справді, якби ми повинні були дивитися на $var
в gbk
наборі символів, ми бачимо:
縗 'АБО 1 = 1 / *
А саме цього вимагає атака.
Запит
Ця частина є лише формальністю, але ось наданий запит:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
Вітаємо, щойно ви успішно атакували програму, використовуючи mysql_real_escape_string()
...
Поганий
Погіршується. PDO
за замовчуванням для емуляції підготовлених операторів з MySQL. Це означає, що на стороні клієнта, він в основному робить спринт через mysql_real_escape_string()
(у бібліотеці С), що означає, що наступне призведе до успішного введення:
$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
не виставляв API C mysql_set_charset()
до 5.3.6, тому в попередніх версіях він не може запобігти цій атаці для кожної можливої команди! Зараз це відкрито як параметр DSN .
Збережуюча благодать
Як ми говорили на початку, для цієї атаки спрацьовує з'єднання з базою даних, використовуючи вразливий набір символів. неutf8mb4
є вразливим, але все ж може підтримувати кожен символ Unicode: ви можете використовувати це замість цього, але він доступний лише з MySQL 5.5.3. Альтернативою є utf8
, яка також не є вразливою і може підтримувати всю багатомовну площину Unicode Basic .
Крім того, ви можете ввімкнути 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()
/ PDO параметр DSN-діаграми (в PHP ≥ 5.3.6)
АБО
- Не використовуйте вразливий набір символів для кодування з'єднання (ви використовуєте лише
utf8
/ latin1
/ ascii
/ тощо)
Ви на 100% безпечні.
Інакше ви вразливі, навіть якщо використовуєтеmysql_real_escape_string()
...