Як я можу запобігти ін'єкції SQL у PHP?


2773

Якщо введення користувача вставляється без змін у запит SQL, програма стає вразливою до ін'єкції SQL , як у наступному прикладі:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Це тому, що користувач може ввести щось на зразок value'); DROP TABLE table;--, і запит стає:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Що можна зробити, щоб цього не сталося?

Відповіді:


8956

Використовуйте підготовлені оператори та параметризовані запити. Це оператори SQL, які надсилаються та аналізуються сервером бази даних окремо від будь-яких параметрів. Таким чином, зловмиснику неможливо ввести шкідливий SQL.

У вас є два варіанти для цього:

  1. Використання PDO (для будь-якого підтримуваного драйвера бази даних):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
    
  2. Використання MySQLi (для MySQL):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // Do something with $row
    }
    

Якщо ви підключаєтесь до бази даних, відмінної від MySQL, є специфічний для драйвера другий варіант, на який ви можете звернутися (наприклад, pg_prepare()і pg_execute()для PostgreSQL). PDO - це універсальний варіант.


Правильне налаштування з'єднання

Зауважте, що при використанні PDOдля доступу до бази даних MySQL реально підготовлені оператори за замовчуванням не використовуються . Щоб виправити це, вам слід відключити емуляцію підготовлених операторів. Приклад створення з'єднання за допомогою PDO:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

У наведеному вище прикладі режим помилок не є строго необхідним, але радимо додати його . Таким чином, сценарій не зупиниться з a, Fatal Errorколи щось піде не так. І це дає можливість розробникові catchбудь-якої помилки, яка є thrown як PDOExceptions.

Обов’язковим є , однак, перший setAttribute()рядок, який повідомляє PDO відключити емуляцію підготовлених операторів та використання реальних підготовлених операторів. Це гарантує, що оператор і значення не будуть проаналізовані PHP перед відправкою на сервер MySQL (не даючи можливому зловмиснику шансу ввести шкідливий SQL).

Хоча ви можете встановити charsetпараметри конструктора, важливо зауважити, що «старіші» версії PHP (до 5.3.6) мовчки ігнорували параметр charset у DSN.


Пояснення

Оператор SQL, який ви prepareпередаєте, аналізується та компілюється сервером баз даних. Вказуючи параметри ( ?або а, іменний параметр, як :nameу наведеному вище прикладі), ви повідомляєте двигуну бази даних, куди потрібно фільтрувати. Тоді, коли ви телефонуєте execute, підготовлений вислів поєднується із вказаними вами параметрами.

Тут важливим є те, що значення параметрів поєднуються з компільованим оператором, а не рядком SQL. Інжекція SQL працює шляхом введення скрипту в включення шкідливих рядків, коли він створює SQL для надсилання до бази даних. Отже, надсилаючи фактичний SQL окремо від параметрів, ви обмежуєте ризик закінчитись тим, чого ви не мали наміру.

Будь-які параметри, які ви надсилаєте під час використання підготовленого оператора, просто будуть розглядатися як рядки (хоча двигун бази даних може зробити деяку оптимізацію, щоб параметри, звичайно, могли також закінчуватися як числа). У наведеному вище прикладі, якщо $nameзмінна містить 'Sarah'; DELETE FROM employeesрезультат, це буде просто пошук рядка "'Sarah'; DELETE FROM employees", і ви не закінчите порожню таблицю .

Ще одна перевага використання підготовлених висловлювань полягає в тому, що якщо ви будете виконувати один і той же оператор багато разів за один і той же сеанс, він буде проаналізований і скомпільований лише один раз, що дає вам певну швидкість.

О, а оскільки ви запитували про те, як це зробити для вставки, ось приклад (використовуючи PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);

Чи можна використовувати підготовлені оператори для динамічних запитів?

Хоча ви все ще можете використовувати підготовлені оператори для параметрів запиту, структура самого динамічного запиту не може бути параметризована, а певні функції запиту неможливо параметризувати.

Для цих конкретних сценаріїв найкраще зробити фільтр білого списку, який обмежує можливі значення.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

86
Додамо лише тому, що я його більше ніде не бачив, інша лінія захисту - це брандмауер веб-додатків (WAF), у якому можуть бути встановлені правила для пошуку нападів на ін'єкцію sql:
jkerak

37
Крім того, офіційна документація mysql_query дозволяє виконати лише один запит, тому будь-який інший запит крім того; ігнорується. Навіть якщо це вже застаріло, існує багато систем під PHP 5.5.0, які можуть використовувати цю функцію. php.net/manual/en/function.mysql-query.php
Randall Valenciano

12
Це погана звичка, але це рішення після проблеми: не тільки для ін'єкцій SQL, але і для будь-якого типу ін'єкцій (наприклад, у отворі для введення шаблону виду у F3 Framework v2), якщо у вас готовий старий веб-сайт або додаток страждає від дефектів ін'єкції, одним із варіантів є переназначення значень ваших суперглобальних заздалегідь визначених варіантів, таких як $ _POST з уникнутими значеннями при завантаженні. За PDO все ще можна вийти (також на сьогоднішні рамки): substr ($ pdo-> quotation ($ str, \ PDO :: PARAM_STR), 1, -1)
Alix

15
У цій відповіді відсутнє пояснення того, що є підготовленим твердженням - одне - це показник ефективності, якщо ви використовуєте багато підготовлених заяв під час запиту, а іноді на нього припадає 10-кратне звернення. Кращим випадком буде використання PDO із вимкненням параметра, але підготовка оператора вимкнена.
доніс

6
Використання PDO краще, якщо ви використовуєте прямий запит, переконайтеся, що ви використовуєте mysqli :: escape_string
Kassem

1652

Застаріле попередження: у зразковому коді цієї відповіді (як і зразок коду запитання) використовується MySQLрозширення PHP , яке було застаріле в PHP 5.5.0 та повністю видалено в PHP 7.0.0.

Попередження про безпеку : Ця відповідь не відповідає найкращим практикам безпеки. Утеча недостатня для запобігання ін'єкції SQL , використовуйте замість цього підготовлені оператори . Використовуйте описану нижче стратегію на свій страх і ризик. (Також mysql_real_escape_string()було видалено в PHP 7.)

Якщо ви використовуєте останню версію PHP, mysql_real_escape_stringпараметр, описаний нижче, більше не буде доступний (хоча mysqli::escape_stringце сучасний еквівалент). Ці mysql_real_escape_stringпараметри мають сенс лише для застарілого коду на старій версії PHP.


У вас є два варіанти - уникнути спеціальних символів у вашому unsafe_variableабо використовувати параметризований запит. Обидва захистили б вас від ін'єкції SQL. Параметризований запит вважається кращою практикою, але для його використання потрібно буде змінити нове розширення MySQL в PHP.

Ми покриємо нижню рядок удару, уникаючи першого.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Дивіться також деталі mysql_real_escape_stringфункції.

Щоб використовувати параметризований запит, вам потрібно використовувати MySQLi, а не функції MySQL . Щоб переписати свій приклад, нам знадобиться щось подібне.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

Ключова функція, яку ви хочете прочитати, була б mysqli::prepare.

Крім того, як підказали інші, вам може бути корисним / простішим збільшити шар абстрагування чимось на зразок PDO .

Зауважте, що випадок, про який ви запитували, є досить простим, і що складніші випадки можуть потребувати складніших підходів. Зокрема:

  • Якщо ви хочете змінити структуру SQL на основі введення користувача, параметризовані запити не допоможуть, а необхідні параметри не закриваються mysql_real_escape_string. У такому випадку вам краще передати вхід користувача через білий список, щоб переконатися, що лише "безпечні" значення не допускаються.
  • Якщо ви використовуєте цілі числа з введення користувачем в умові і використовуєте mysql_real_escape_stringпідхід, ви будете страждати від проблеми, описаної Polynomial в коментарях нижче. Цей випадок складніший, оскільки цілі числа не будуть оточені лапками, тому ви можете вирішити, перевіривши, що введення користувача містить лише цифри.
  • Вірогідні й інші випадки, про які я не знаю. Ви можете виявити, що це корисний ресурс щодо деяких більш тонких проблем, з якими ви можете зіткнутися.

1
використання mysql_real_escape_stringдостатньо або я також повинен використовувати параметризований?
peiman F.

5
@peimanF. зберігайте хорошу практику використання параметризованих запитів навіть у локальному проекті. Завдяки параметризованим запитам ви гарантуєте, що не буде введення SQL. Але майте на увазі, що вам слід обробити дані, щоб уникнути фальшивого пошуку (тобто введення XSS, наприклад, введення HTML-коду в текст), htmlentitiesнаприклад
Goufalite

2
@peimanF. Хороша практика параметризованих запитів і прив'язки значень, але реальна рядок виходу на сьогодні добре
Річард

Я розумію включення mysql_real_escape_string()для повноти, але не є прихильником перераховувати спочатку найбільш схильний до помилок підхід. Читач може просто швидко схопити перший приклад. Добре, що зараз застаріло :)
Steen Schütt

1
@ SteenSchütt - Усі mysql_*функції застарілі. Їх замінили подібні mysqli_* функції, такі як mysqli_real_escape_string.
Рік Джеймс

1073

Кожна відповідь тут охоплює лише частину проблеми. Насправді є чотири різні частини запиту, які ми можемо динамічно додавати до SQL:

  • рядок
  • число
  • ідентифікатор
  • синтаксичне ключове слово

І підготовлені заяви охоплюють лише дві з них.

Але іноді доводиться робити наш запит ще більш динамічним, додаючи також операторів чи ідентифікаторів. Отже, нам знадобляться різні методи захисту.

Взагалі такий підхід до захисту базується на списках .

У цьому випадку кожен динамічний параметр повинен бути жорстко закодований у вашому сценарії та обраний із цього набору. Наприклад, щоб зробити динамічне замовлення:

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

Щоб полегшити процес, я написав функцію помічника білого списку, яка виконує всю роботу в одному рядку:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

Є ще один спосіб захистити ідентифікатори - втеча, але я скоріше дотримуюся білого списку як більш надійного та явного підходу. Тим не менше, поки у вас є кодований ідентифікатор, ви можете уникати символу цитати, щоб зробити його безпечним. Наприклад, за умовчанням для mysql вам потрібно подвоїти символ цитати, щоб уникнути цього . Для інших інших правил виходу з СУБД було б іншим.

Тим НЕ менше, існує проблема з SQL синтаксису ключових слів (наприклад AND, DESCі такі), але білий список , здається , єдиний підхід в даному випадку.

Отже, загальну рекомендацію можна викласти як

  • Будь-яка змінна, яка являє собою буква SQL даних, (або, простіше кажучи - рядок SQL або число), повинна бути додана через підготовлений оператор. Немає винятків.
  • Будь-яка інша частина запиту, наприклад, ключове слово SQL, таблиця або ім’я поля чи оператор, повинні бути відфільтровані через білий список.

Оновлення

Хоча існує загальна згода щодо найкращих практик щодо захисту від ін'єкцій SQL, також існує багато поганих практик. А деякі з них занадто глибоко вкорінені у свідомості користувачів PHP. Наприклад, на цій самій сторінці є (хоча невидима для більшості відвідувачів) понад 80 видалених відповідей - усі видалені громадою через погану якість або пропагування поганих та застарілих практик. Що ще гірше, деякі погані відповіді не видаляються, а процвітають.

Наприклад, є (1) ще (2) (3) багато (4) відповідей (5) , включаючи другий найбільш схвалений відповідь, який пропонує вам уникнути ручного виходу з рядка - застарілий підхід, який виявляється незахищеним.

Або є трохи краща відповідь, яка пропонує просто ще один метод форматування рядків і навіть може похвалитися цим як кінцевою панацеєю. Хоча, звичайно, це не так. Цей метод не є кращим, ніж звичайне форматування рядків, але він зберігає всі свої недоліки: він застосовний лише до рядків і, як і будь-яке інше ручне форматування, по суті є необов'язковим, необов'язковим заходом, схильним до людських помилок будь-якого типу.

Я думаю, що все це через одне дуже давнє забобонство, підтримуване такими органами влади, як OWASP або керівництво PHP , яке проголошує рівність між будь-якими "втечами" та захистом від ін'єкцій SQL.

Незалежно від того, що говорив посібник PHP протягом віків, *_escape_stringжодним чином не забезпечує безпеку даних і ніколи не призначався. Окрім того, що він не є корисним для будь-якої частини SQL, крім рядка, ручне вимкнення є помилковим, оскільки воно вручну, як і протилежне автоматизованому.

А OWASP робить це ще гірше, наголошуючи на уникненні вводу користувачів, що є цілковитою дурницею: таких слів у контексті захисту від ін'єкцій не повинно бути. Кожна змінна потенційно небезпечна - незалежно від джерела! Або, іншими словами - кожна змінна повинна бути належним чином відформатована, щоб розмістити її в запиті - незалежно від джерела. Це призначення має значення. У той момент, коли розробник починає відокремлювати овець від кіз (думаючи, чи є якась певна змінна «безпечною» чи ні), він / вона робить свій перший крок до катастрофи. Не кажучи вже про те, що навіть формулювання передбачає, що групова втеча у точці входу, нагадує особливість магічних цитат - уже знехтувала, застаріла та вилучена.

Таким чином, на відміну від будь-яких "уникнути", підготовлені заяви - це міра, яка дійсно захищає від ін'єкції SQL (коли це застосовується).


848

Я рекомендую використовувати PDO (PHP Data Objects) для запуску параметризованих SQL-запитів.

Це не тільки захищає від ін'єкції SQL, але й прискорює запити.

І за допомогою PDO , а не mysql_, mysqli_і pgsql_функції, ви зробите ваше додаток трохи абстрагується з бази даних, в поодинокі випадки що ви повинні постачальникам база даних комутаторів.


619

Використання PDOта підготовлені запити.

( $connє PDOоб’єктом)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

549

Як бачите, люди пропонують вам використовувати максимум підготовлені заяви. Це неправильно, але коли ваш запит виконується лише один раз за кожний процес, буде незначне покарання за ефективність.

Я стикався з цим питанням, але, думаю, я вирішив це дуже витончено - так, як хакери використовують, щоб уникнути використання цитат. Я використовував це спільно з імітованими підготовленими висловлюваннями. Я використовую його , щоб запобігти всі види можливих атак з впровадженням SQL.

Мій підхід:

  • Якщо ви очікуєте, що вхід буде цілим, переконайтеся, що він дійсно цілий. Для мови змінного типу, як PHP, це дуже важливо. Ви можете використовувати, наприклад, це дуже просте, але потужне рішення:sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Якщо ви очікуєте чогось іншого від цілого шістнадцяткового . Якщо ви будете це шістнадцятковий, ви прекрасно уникнете всіх даних. У C / C ++ є функція, яка називається mysql_hex_string(), в PHP ви можете використовувати bin2hex().

    Не хвилюйтеся з приводу того, що рядок, що увійшов, матиме розмір 2x у початковій довжині, тому що навіть якщо ви використовуєте mysql_real_escape_string, PHP повинен виділяти ту саму ємність ((2*input_length)+1), яка однакова.

  • Цей шістнадцятковий метод часто застосовується при передачі двійкових даних, але я не бачу причини, щоб не використовувати його на всіх даних для запобігання атак ін'єкції SQL. Зверніть увагу, що вам потрібно додати дані 0xабо використовувати функцію MySQL UNHEXзамість цього.

Так, наприклад, запит:

SELECT password FROM users WHERE name = 'root'

Стане:

SELECT password FROM users WHERE name = 0x726f6f74

або

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex - ідеальна втеча. Ніякого способу ін’єкції.

Різниця між функцією UNHEX та префіксом 0x

Була якась дискусія в коментарях, тому я, нарешті, хочу зробити це зрозумілим. Ці два підходи дуже схожі, але вони дещо відрізняються:

Префікс ** 0x ** може використовуватися лише для стовпців даних, таких як char, varchar, текст, блок, двійкові тощо .
Також його використання трохи складніше, якщо ви збираєтесь вставити порожній рядок. Вам доведеться повністю замінити його '', інакше ви отримаєте помилку.

UNHEX () працює на будь-якій колонці; вам не доведеться турбуватися про порожній рядок.


Шестнадцяткові методи часто використовуються як напади

Зауважте, що цей шістнадцятковий метод часто використовується як атака ін'єкцій SQL, де цілі числа - це як рядки, і уникнуті саме за допомогою mysql_real_escape_string. Тоді ви можете уникнути використання цитат.

Наприклад, якщо ви просто зробите щось подібне:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

напад може ввести вас дуже легко . Розглянемо наступний введений код, повернутий із вашого сценарію:

SELECT ... WHERE id = -1 об'єднання - все вибирати ім'я таблиці з information_schema.tables

а тепер просто витягніть структуру таблиці:

SELECT ... WHERE id = -1 об'єднання всіх виберіть ім'я стовпця з інформаційної колонки information_schema.column, де table_name = 0x61727469636c65

А потім просто виберіть потрібні дані. Хіба це не круто?

Але якщо кодер сайту для ін'єкцій зробить це шістнадцятковим, ін'єкція не буде можливою, оскільки запит виглядатиме так: SELECT ... WHERE id = UNHEX('2d312075...3635')


6
@SumitGupta Так, ти. MySQL не поєднується з, +але з CONCAT. І до продуктивності: я не думаю, що це впливає на продуктивність, тому що mysql повинен проаналізувати дані, і не має значення, якщо походження є рядковим або шістнадцятковим
Zaffy

11
@YourCommonSense Ви не розумієте поняття ... Якщо ви хочете мати рядок у mysql, ви цитуєте його так, 'root'або ви можете вкласти його у шістнадцятку, 0x726f6f74але якщо ви хочете номер та надішлете його як рядок, ви, ймовірно, напишете "42", а не CHAR (42 ) ... '42' в шістнадцятковому б 0x3432НЕ0x42
Zaffy

7
@YourCommonSense Я нічого не маю сказати ... просто хай ... якщо ти все ще хочеш спробувати шістнадцятковий номер у цифрових полях, дивись другий коментар. З вами обділююся, що це спрацює.
Zaffy

2
У відповіді чітко зазначено, що це не буде працювати таким чином з цілими значеннями, тому що bin2hex перетворює передане значення в рядок (і таким чином bin2hex (0) дорівнює 0x30, а не 0x03) - це, ймовірно, та частина, яка вас бентежить . Якщо ви дотримуєтесь цього, він працює чудово (принаймні, на моєму сайті, протестований з 4 різними версіями mysql на машинах debian, 5.1.x до 5.6.x) Адже шістнадцятковий - це лише спосіб представлення, а не значення;)
griffin

5
@YourCommonSense ви все ще не розумієте? Ви не можете використовувати 0x і concat, тому що якщо рядок порожній, ви закінчитеся з помилкою. Якщо ви хочете просту альтернативу вашому запиту, спробуйте цей варіантSELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy

499

Застаріле попередження: у зразковому коді цієї відповіді (як і зразок коду запитання) використовується MySQLрозширення PHP , яке було застаріле в PHP 5.5.0 та повністю видалено в PHP 7.0.0.

Попередження про безпеку : Ця відповідь не відповідає найкращим практикам безпеки. Утеча недостатня для запобігання ін'єкції SQL , використовуйте замість цього підготовлені оператори . Використовуйте описану нижче стратегію на свій страх і ризик. (Також mysql_real_escape_string()було видалено в PHP 7.)

ВАЖЛИВО

Найкращий спосіб запобігти ін'єкції SQL - це використання підготовлених заяв, а не втеча , як свідчить прийнята відповідь .

Існують такі бібліотеки, як Aura.Sql та EasyDB, які дозволяють розробникам легше використовувати підготовлені заяви. Щоб дізнатися більше про те, чому підготовлені операції краще зупинити введення SQL , зверніться до цього mysql_real_escape_string()обходу та нещодавно виправлених уразливостей Unicode SQL Injection в WordPress .

Запобігання ін'єкцій - mysql_real_escape_string ()

PHP має спеціально розроблену функцію для запобігання цих атак. Все , що вам потрібно зробити , це використовувати повний рот функції, mysql_real_escape_string.

mysql_real_escape_stringприймає рядок, який буде використовуватися в запиті MySQL, і повертає ту саму рядок зі всіма спробами введення SQL, які безпечно не були виконані. В основному це замінить ті клопітні лапки ('), які користувач може ввести, безпечним замінником MySQL, втеченою цитатою \'.

ПРИМІТКА: Ви повинні бути підключені до бази даних, щоб використовувати цю функцію!

// Підключення до MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Більш детальну інформацію ви можете знайти в MySQL - запобігання ін'єкцій SQL .


30
Це найкраще, що ви можете зробити зі спадщиною розширення mysql. Для нового коду рекомендується перейти на mysqli або PDO.
Альваро Гонсалес

7
Я не згоден з цією «спеціально створеною функцією для запобігання цих атак». Я думаю, що mysql_real_escape_stringмета полягає в тому, щоб дозволити побудувати правильний SQL-запит для кожного вхідного рядка даних. Профілактика sql-ін’єкцій є побічним ефектом цієї функції.
секта

4
Ви не використовуєте функції для написання правильних рядків вхідних даних. Ви просто пишете правильні, яким не потрібно бігти або вже були втечені. mysql_real_escape_string (), можливо, був розроблений з метою, про яку ви згадуєте, але єдине його значення - запобігання ін'єкцій.
Наска

17
УВАГА! mysql_real_escape_string() не є безпогрішним .
eggyal

9
mysql_real_escape_stringтепер застаріла, тому її варіант уже не є життєздатним. В майбутньому це буде видалено з PHP. Найкраще перейти до того, що рекомендують люди PHP або MySQL.
jww

462

Ви можете зробити щось базове, як це:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Це не вирішить кожну проблему, але це дуже хороший крок. Я не залишив очевидних елементів, таких як перевірка існування, формату змінної (цифри, літери тощо).


28
Я спробував ваш приклад, і це для мене добре. Чи можете ви зрозуміти, "це не вирішить кожну проблему"
Chinook

14
Якщо ви не цитуєте рядок, вона все-таки ін'єкційна. Візьмемо $q = "SELECT col FROM tbl WHERE x = $safe_var";для прикладу. Налаштування $safe_varна 1 UNION SELECT password FROM usersроботу в цьому випадку через відсутність цитат. Також можна вставити рядки в запит за допомогою CONCATі CHR.
Поліном

4
@Polynomial Цілком правильно, але я вважаю це просто неправильним використанням. Поки ви ним правильно користуєтеся, воно обов'язково спрацює.
glglgl

22
УВАГА! mysql_real_escape_string() не є безпогрішним .
eggyal

8
mysql_real_escape_stringтепер застаріла, тому її варіант уже не є життєздатним. В майбутньому це буде видалено з PHP. Найкраще перейти до того, що рекомендують люди PHP або MySQL.
jww

380

Що б ви в кінцевому підсумку не використовували, переконайтеся, що ви перевірили, чи не введені вами дані magic_quotesчи якийсь добре значущий сміття, і, якщо потрібно, запустіть його stripslashesчи будь-що, щоб провести його санітарію.


11
Дійсно; Біг із увімкненими котиками magic_quotes просто сприяє поганій практиці. Однак іноді ви не завжди можете контролювати навколишнє середовище до цього рівня - або у вас немає доступу до управління сервером, або ваша програма повинна співіснувати з програмами, які (здригаються) залежать від такої конфігурації. З цих причин добре писати портативні програми, хоча, очевидно, витрачаються зусилля, якщо ви керуєте середовищем розгортання, наприклад, тому, що це власне додаток або використовується лише у вашому конкретному середовищі.
Роб

24
Станом на PHP 5.4, гидота, відома як "магічні цитати", вбита мертвою . І добре позбавлення від поганого сміття.
BryanH

363

Застаріле попередження: у зразковому коді цієї відповіді (як і зразок коду запитання) використовується MySQLрозширення PHP , яке було застаріле в PHP 5.5.0 та повністю видалено в PHP 7.0.0.

Попередження про безпеку : Ця відповідь не відповідає найкращим практикам безпеки. Утеча недостатня для запобігання ін'єкції SQL , використовуйте замість цього підготовлені оператори . Використовуйте описану нижче стратегію на свій страх і ризик. (Також mysql_real_escape_string()було видалено в PHP 7.)

Параметризована перевірка запиту ТА введення - це шлях. Існує багато сценаріїв, за яких може відбуватися ін'єкція SQL, хоча mysql_real_escape_string()вона і використовувалася.

Ці приклади вразливі до ін'єкції SQL:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

або

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

В обох випадках ви не можете використовувати 'для захисту інкапсуляції.

Джерело : Несподівана ін'єкція SQL (коли втеча недостатньо)


2
Ви можете запобігти введенню SQL, якщо ви застосуєте техніку перевірки вводу, в якій введення користувача аутентифіковано набір визначених правил щодо довжини, типу та синтаксису, а також проти бізнес-правил.
Йосип Івич

312

На мою думку, найкращий спосіб уникнути ін'єкції SQL у вашій PHP-програмі (або будь-якій веб-програмі з цього приводу) - подумати про архітектуру вашого додатка. Якщо єдиний спосіб захисту від ін'єкцій SQL - це пам’ятати про використання спеціального методу чи функції, яка робить «Правильну річ» кожного разу, коли ви спілкуєтесь із базою даних, ви робите це неправильно. Таким чином, це лише питання часу, поки ви не забудете правильно відформатувати запит у якийсь момент свого коду.

Прийняття шаблону MVC та такої рамки, як CakePHP або CodeIgniter , мабуть, є правильним шляхом: загальні завдання, такі як створення захищених запитів до бази даних, вирішені та централізовано реалізовані в таких рамках. Вони допомагають точніше організувати веб-додаток і змушують думати більше про завантаження та збереження об’єктів, ніж про надійну побудову одиночних запитів SQL.


5
Я думаю, що ваш перший абзац важливий. Розуміння є ключовим. Також усі не працюють на компанію. Для великої кількості людей рамки насправді йдуть проти ідеї розуміння . Наблизившись до основи, можливо, не варто цінувати, працюючи в термін, але робітники, які роблять там, із задоволенням забруднюють руки. Розробники рамок не настільки привілейовані, що всі інші повинні поклонитися і припустити, що ніколи не помиляються. Сила для прийняття рішень все ще важлива. Хто скаже, що в майбутньому моя структура не замінить якусь іншу схему?
Ентоні Рутлідж

@AnthonyRutledge Ви абсолютно праві. Дуже важливо зрозуміти, що відбувається і чому. Однак ймовірність того, що справді перевірений та активно використаний та розроблений фреймворк натрапив на багато проблем і вирішив безліч дір у безпеці, досить високий. Добре заглянути в джерело, щоб відчути якість коду. Якщо це неперевірений безлад, мабуть, це не безпечно.
Йоганнес Фаренкруг

3
Ось Ось Хороші бали. Однак, чи погоджуєтесь ви, що багато людей можуть вивчити та навчитися приймати систему MVC, але не кожен може відтворити її вручну (контролери та сервер). З цим питанням можна зайти занадто далеко. Чи потрібно мені розуміти свою мікрохвильову піч, перш ніж нагріти печиво з арахісовим маслом печиво, яке зробила моя подруга? ;-)
Ентоні Рутледж

3
@AnthonyRutledge Я згоден! Я думаю, що також має значення сенс використання: чи будую я фотогалерею для своєї особистої домашньої сторінки чи будую веб-додаток для інтернет-банкінгу? В останньому випадку дуже важливо зрозуміти деталі безпеки та те, як рамки, якими я користуюся, вирішують їх.
Йоханнес Фаренкруг

3
Ах, виняток із безпеки - це зробити самостійно. Розумієте, я схильний бути ризиком все це та йти на перерву. :-) Жартую. Маючи достатньо часу, люди можуть навчитися робити досить захищений додаток. Занадто багато людей поспішають. Вони закидають руки вгору і вважають, що рамки безпечніші . Адже їм не вистачає часу для тестування та з'ясування речей. Більше того, безпека - це сфера, яка потребує спеціального вивчення. Це не те, що просто програмісти знають глибоко завдяки розумінням алгоритмів та моделей дизайну.
Ентоні Рутлідж

298

Я віддаю перевагу збереженим процедурам ( MySQL підтримує збережені процедури з 5.0 ) з точки зору безпеки - переваги -

  1. Більшість баз даних (включаючи MySQL ) дозволяють обмежувати доступ користувачів до виконання збережених процедур. Дрібнозернистий контроль доступу до системи безпеки корисний для запобігання ескалації атак на привілеї. Це запобігає можливості компрометованих додатків запускати SQL безпосередньо проти бази даних.
  2. Вони абстрагують необроблений SQL-запит із програми, щоб менше інформації про структуру бази даних було доступно для програми. Це ускладнює людям складніше розуміти базову структуру бази даних та проектувати відповідні атаки.
  3. Вони приймають лише параметри, тому переваги параметризованих запитів є. Звичайно - IMO вам все-таки потрібно очистити свої дані - особливо якщо ви використовуєте динамічний SQL всередині збереженої процедури.

Недоліками є -

  1. Вони (зберігаються процедури) важко підтримувати і, як правило, розмножуються дуже швидко. Це робить управління ними проблемою.
  2. Вони не дуже підходять для динамічних запитів - якщо вони побудовані для прийняття динамічного коду як параметрів, то багато переваг заперечуються.

297

Існує багато способів запобігання ін'єкцій SQL та інших SQL-хак. Ви можете легко знайти його в Інтернеті (Пошук Google). Звичайно, PDO - одне з хороших рішень. Але я хотів би запропонувати вам кілька хороших посилань на запобігання від ін'єкції SQL.

Що таке ін'єкція SQL і як запобігти

Посібник PHP для ін'єкції SQL

Пояснення Microsoft введення та профілактики SQL у PHP

І деякі інші, як запобігання ін'єкції SQL за допомогою MySQL та PHP .

Тепер, чому вам потрібно запобігти запиту від ін'єкції SQL?

Я хотів би повідомити вас: Чому ми намагаємося запобігти ін'єкції SQL на короткому прикладі нижче:

Запит на відповідність автентифікації входу:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

Тепер, якщо хтось (хакер) ставить

$_POST['email']= admin@emali.com' OR '1=1

і пароль що-небудь ....

Запит буде проаналізований у системі лише до:

$query="select * from users where email='admin@emali.com' OR '1=1';

Інша частина буде відкинута. Отже, що буде? Неавторизований користувач (хакер) зможе увійти як адміністратор, не маючи свого пароля. Тепер він / вона може робити все, що може зробити адміністратор / електронна пошта. Дивіться, це дуже небезпечно, якщо ін'єкцію SQL не запобігти.


267

Я думаю, якщо хтось хоче використовувати PHP та MySQL чи якийсь інший сервер баз даних:

  1. Подумайте про вивчення PDO (PHP Data Objects) - це рівень доступу до бази даних, що забезпечує єдиний метод доступу до декількох баз даних.
  2. Подумайте про вивчення MySQLi
  3. Використовуйте нативні функції PHP, такі як: strip_tags , mysql_real_escape_string або просто, якщо змінна числова, просто (int)$foo. Детальніше про тип змінних у PHP читайте тут . Якщо ви використовуєте бібліотеки, такі як PDO або MySQLi, завжди використовуйте PDO :: quote () та mysqli_real_escape_string () .

Приклади бібліотек:

---- PDO

----- Немає заповнювачів - дозріла для ін'єкції SQL! Це погано

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- Безіменні заповнювачі

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Іменовані заповнювачі

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

PS :

PDO виграє цю битву легко. Підтримуючи дванадцять різних драйверів баз даних та названих параметрів, ми можемо ігнорувати невеликі втрати продуктивності та звикати до його API. З точки зору безпеки, вони обоє безпечні, якщо розробник використовує їх так, як вони повинні використовуватись

Але в той час як PDO і MySQLi досить швидкі, MySQLi виконує незначно швидше в показниках - ~ 2,5% для не підготовлених заяв і ~ 6,5% для підготовлених.

І будь ласка, протестуйте кожен запит до вашої бази даних - це кращий спосіб запобігти ін’єкції.


що mysqli невірно. Перший парам виражає типи даних.
mickmackusa

257

Якщо можливо, введіть типи своїх параметрів. Але це працює лише над простими типами, такими як int, bool та float.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

3
Це один з небагатьох випадків, коли я використовував би «уникнуте значення» замість підготовленого оператора. А цілочисельне перетворення типів є надзвичайно ефективним.
HoldOffHunger

233

Якщо ви хочете скористатися кеш-движками, такими як Redis або Memcached , можливо DALMP може стати вибором. Тут використовується чистий MySQLi . Перевірте це: шар абстракції бази даних DALMP для MySQL за допомогою PHP.

Крім того, ви можете "підготувати" свої аргументи перед підготовкою запиту, щоб ви могли будувати динамічні запити і в кінці мати повністю підготовлений запит операторів. Шар абстракції бази даних DALMP для MySQL за допомогою PHP.


224

Для тих, хто не знає, як використовувати PDO (виходячи з mysql_функцій), я зробив дуже-дуже просту обгортку PDO, яка є одним файлом. Існує, щоб показати, як легко робити всі звичайні речі, які потрібно робити. Працює з PostgreSQL, MySQL та SQLite.

В принципі, читати його в той час як ви читаєте керівництво , щоб побачити , як поставити функції PDO для використання в реальному житті , щоб зробити його простим для зберігання та вилучення значень в форматі ви хочете.

Я хочу одну колонку

$count = DB::column('SELECT COUNT(*) FROM `user`);

Я хочу отримати результати масиву (ключ => значення) (тобто для створення вибору)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Я хочу отримати результат одного ряду

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Я хочу масив результатів

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

222

За допомогою цієї функції PHP mysql_escape_string()можна швидко отримати хорошу профілактику.

Наприклад:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - Виключає рядок для використання в mysql_query

Для більшої профілактики ви можете додати в кінці ...

wHERE 1=1   or  LIMIT 1

Нарешті ви отримуєте:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

220

Кілька рекомендацій щодо виходу спеціальних символів у операторах SQL.

Не використовуйте MySQL . Це розширення застаріле. Використовуйте замість MySQLi або PDO .

MySQLi

Для втечі спеціальних символів в рядку ви можете використовувати функцію mysqli_real_escape_string . Функція не працюватиме належним чином, якщо не встановлено правильний набір символів за допомогою mysqli_set_charset .

Приклад:

$mysqli = new mysqli('host', 'user', 'password', 'database');
$mysqli->set_charset('charset');

$string = $mysqli->real_escape_string($string);
$mysqli->query("INSERT INTO table (column) VALUES ('$string')");

Для автоматичного виходу значень з підготовленими операторами використовуйте mysqli_prepare та mysqli_stmt_bind_param, де типи відповідних змінних прив'язки повинні бути надані для відповідного перетворення:

Приклад:

$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

Незалежно від того, використовуєте ви підготовлені висловлювання, або mysqli_real_escape_stringви завжди повинні знати тип вхідних даних, з якими працюєте.

Отже, якщо ви використовуєте підготовлений оператор, ви повинні вказати типи змінних для mysqli_stmt_bind_paramфункції.

І використання mysqli_real_escape_string, як видно з назви, призначене для уникнення спеціальних символів у рядку, тому це не зробить цілі числа безпечними. Метою цієї функції є запобігання розриву рядків у операторах SQL та пошкодження бази даних, яку вона може спричинити. mysqli_real_escape_stringє корисною функцією при правильному використанні, особливо в поєднанні з sprintf.

Приклад:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

3
Питання дуже загальне. Деякі чудові відповіді вище, але більшість пропонують підготовлені заяви. MySQLi async не підтримує підготовлені заяви, тому спринт виглядає чудовим варіантом для цієї ситуації.
Дастін Грем

183

Просту альтернативу цій проблемі можна було б вирішити шляхом надання відповідних дозволів у самій базі даних. Наприклад: якщо ви використовуєте базу даних MySQL, тоді увійдіть до бази даних через термінал або наданий інтерфейс користувача та просто виконайте цю команду:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

Це обмежить користувача лише обмежуватися лише вказаним запитом. Видаліть дозвіл на видалення, і таким чином дані ніколи не будуть видалені з запиту, запущеного зі сторінки PHP. Друге, що потрібно зробити - це змити привілеї, щоб MySQL оновлював дозволи та оновлення.

FLUSH PRIVILEGES; 

більше інформації про промивання .

Щоб побачити поточні привілеї для користувача, запустіть такий запит.

select * from mysql.user where User='username';

Дізнайтеся більше про GRANT .


25
Ця відповідь по суті є помилковою , оскільки не допомагає запобігти ін'єкційним шляхом, а просто намагається пом’якшити наслідки. Марно.
Твій здоровий глузд

1
Правильно, це не забезпечує рішення, але це те, що ви можете зробити перед тим, як уникнути речей.
Апурв Нерлекар

1
@Apurv Якщо моя мета - читати приватну інформацію з вашої бази даних, то не маючи дозволу DELETE нічого не означає.
Алекс Холсгроув

1
@AlexHolsgrove: Не поспішайте, я просто пропонував добрі практики пом'якшення наслідків.
Апурв Нерлекар

2
@Apurv Ви не хочете "пом'якшувати наслідки", ви хочете зробити все можливе для захисту від цього. Щоб бути справедливим, важливо встановити правильний доступ користувача, але насправді не те, про що вимагає ОП.
Алекс Холсгроув

177

Щодо багатьох корисних відповідей, я сподіваюся додати певної цінності цій темі.

SQL-ін'єкція - це атака, яку можна здійснити за допомогою вводу користувача (входи, заповнені користувачем, а потім використані всередині запитів). Шаблони введення SQL - це правильний синтаксис запитів, хоча ми можемо це назвати: погані запити з поганих причин, і ми припускаємо, що може виявитися недобросовісна людина, яка намагається отримати секретну інформацію (минаючи контроль доступу), яка впливає на три принципи безпеки (конфіденційність) , цілісність та доступність).

Тепер наша суть полягає в тому, щоб запобігти загрозам безпеці, таким як атаки на ін'єкцію SQL, запитання (як запобігти атаці ін'єкції SQL за допомогою PHP) бути більш реалістичним: фільтрація даних або очищення вхідних даних є випадком, коли використовується введені користувачем дані всередині такий запит, використовуючи PHP або будь-яку іншу мову програмування, не є випадком, або, як рекомендують більшість людей використовувати сучасні технології, такі як підготовлена ​​заява чи будь-які інші інструменти, що підтримують на даний момент запобігання ін'єкцій SQL, вважають, що ці інструменти вже недоступні? Як убезпечити свою заявку?

Мій підхід проти введення SQL полягає в: очищенні даних, що вводяться користувачем, перед тим, як надсилати їх до бази даних (перед тим, як використовувати їх у будь-якому запиті).

Фільтрація даних для (перетворення небезпечних даних у безпечні дані)

Врахуйте, що PDO та MySQLi недоступні. Як убезпечити свою заявку? Ви змушуєте мене їх використовувати? Що щодо інших мов, окрім PHP? Я вважаю за краще надавати загальні ідеї, оскільки їх можна використовувати для ширших кордонів, а не лише для конкретної мови.

  1. Користувач SQL (обмеження привілею користувача): найпоширеніші операції SQL - це (SELECT, UPDATE, INSERT), то чому б надавати привілею UPDATE користувачеві, який цього не потребує? Наприклад, для входу в систему та сторінки пошуку використовуються лише SELECT, чому тоді користувачі DB на цих сторінках з високими привілеями?

ПРАВИЛА: не створюйте одного користувача бази даних для всіх привілеїв. Для всіх операцій SQL ви можете створити схему типу (deluser, selectuser, updateuser) як імена користувачів для зручного використання.

Дивіться принцип найменшої пільги .

  1. Фільтрація даних: перед тим, як створити будь-який запит користувача, його слід перевірити та відфільтрувати. Для програмістів важливо визначити деякі властивості для кожної змінної введення користувача: тип даних, шаблон даних та довжина даних . Поле, яке є числом між (x і y), повинно бути точно підтверджене за допомогою точного правила, а для поля, що є рядком (текстом): шаблон є випадком, наприклад, ім'я користувача повинно містити лише деякі символи, давайте скажіть [a-zA-Z0-9_-.]. Довжина змінюється між (x і n), де x і n (цілі числа, x <= n). Правило: створення точних фільтрів та правил перевірки - найкраща практика для мене.

  2. Використовуйте інші інструменти: Тут я також погоджуся з вами, що підготовлений оператор (параметризований запит) та збережені процедури. Недоліками в тому, що ці способи вимагають передових навичок, яких не існує для більшості користувачів. Основна ідея тут полягає в тому, щоб розрізняти запит SQL і дані, які використовуються всередині. Обидва підходи можна використовувати навіть із небезпечними даними, оскільки введені тут дані даних нічого не додають до початкового запиту, наприклад (будь-який або x = x).

Для отримання додаткової інформації, будь ласка, прочитайте чит-лист для запобігання ін'єкцій OWASP SQL .

Тепер, якщо ви є досвідченим користувачем, почніть використовувати цю захист так, як вам подобається, але для початківців, якщо вони не можуть швидко реалізувати збережену процедуру та підготовлену заяву, краще відфільтрувати вхідні дані настільки, наскільки вони можуть.

Нарешті, розглянемо, що користувач надсилає цей текст нижче, а не вводить своє ім’я користувача:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

Цей вхід можна перевірити на ранньому етапі без будь-яких підготовлених операторів та збережених процедур, але бути на захищеній стороні, використовуючи їх, починається після фільтрації та перевірки даних користувачів.

Останній пункт - виявлення несподіваної поведінки, яка вимагає більше зусиль і складності; не рекомендується для звичайних веб-додатків.

Несподівана поведінка у наведеному вище введенні користувача - SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA та root. Після виявлення цих слів ви можете уникнути введення.

ОНОВЛЕННЯ 1:

Користувач прокоментував, що ця публікація марна, гаразд! Ось що надав OWASP.ORG :

Основні засоби захисту:

Варіант №1: Використання підготовлених заяв (параметризовані запити)
Варіант №2: Використання збережених процедур
Варіант №3: Уникнення всього введеного користувачем вводу

Додаткові засоби захисту:

Також виконувати: Найменший привілей
Також виконувати: Білий список Введення перевірки

Як ви знаєте, претензія на статтю повинна підтримуватися вагомим аргументом, принаймні однією посиланням! В іншому випадку це розглядається як напад і погана претензія!

Оновлення 2:

З посібника PHP, PHP: Підготовлені заяви - Посібник :

Утеча та ін'єкція SQL

Обмежені змінні будуть автоматично виведені сервером. Сервер вставляє свої уникнуті значення у відповідних місцях у шаблон заяви перед виконанням. Серверу необхідно надати підказку щодо типу зв'язаної змінної, щоб створити відповідне перетворення. Для отримання додаткової інформації див. Функцію mysqli_stmt_bind_param ().

Автоматичне вимкнення значень на сервері іноді вважається функцією захисту для запобігання введення SQL. Такий же ступінь безпеки може бути досягнутий і з не підготовленими висловлюваннями, якщо введені значення будуть правильними.

Оновлення 3:

Я створив тестові випадки, щоб знати, як PDO та MySQLi надсилають запит на сервер MySQL при використанні підготовленого оператора:

PDO:

$user = "''1''"; // Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Журнал запитів:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Журнал запитів:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

Зрозуміло, що підготовлена ​​заява також уникає даних, нічого іншого.

Як також було зазначено у вищезазначеному твердженні,

Автоматичне вимкнення значень на сервері іноді вважається функцією захисту для запобігання введення SQL. Такий же ступінь безпеки може бути досягнутий і з не підготовленими висловлюваннями, якщо введені значення будуть правильними

Таким чином, це доводить, що перевірка даних, така як intval()хороша ідея для цілих значень, перш ніж надсилати будь-який запит. Крім того, запобігання зловмисним даним користувачів перед надсиланням запиту є правильним та коректним підходом .

Будь ласка, дивіться це питання для більш детальної інформації: PDO надсилає необроблений запит на MySQL, тоді як Mysqli надсилає підготовлений запит, обидва дають однаковий результат

Список літератури:

  1. SQL Injection Cheat Sheet
  2. Інжекція SQL
  3. Інформаційна безпека
  4. Принципи безпеки
  5. Перевірка даних

175

Попередження про безпеку : Ця відповідь не відповідає найкращим практикам безпеки. Утеча недостатня для запобігання ін'єкції SQL , використовуйте замість цього підготовлені оператори . Використовуйте описану нижче стратегію на свій страх і ризик. (Також mysql_real_escape_string()було видалено в PHP 7.)

Застаріле попередження : Розширення mysql наразі застаріле. рекомендуємо використовувати розширення PDO

Я використовую три різні способи, щоб запобігти вразливості мого веб-додатку до ін'єкції SQL.

  1. Використання mysql_real_escape_string(), яке є заздалегідь визначеною функцією в PHP , і цей код оного зворотний слеш наступних символи: \x00, \n, \r, \, ', "і \x1a. Передайте вхідні значення як параметри, щоб мінімізувати шанс введення SQL.
  2. Найдосконаліший спосіб - використання PDO.

Я сподіваюся, що це вам допоможе.

Розглянемо наступний запит:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string () тут не захистить. Якщо ви використовуєте одиничні лапки ('') навколо змінних всередині вашого запиту, це захищає вас від цього. Ось рішення для цього нижче:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

На це питання є кілька хороших відповідей на це.

Я пропоную використовувати PDO - найкращий варіант.

Редагувати:

mysql_real_escape_string()застаріло, ніж у PHP 5.5.0. Використовуйте або mysqli, або PDO.

Альтернативою mysql_real_escape_string () є

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Приклад:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");

169

Найпростішим способом було б використовувати рамки PHP, такі як CodeIgniter або Laravel, які мають вбудовані функції, такі як фільтрування та активні записи, щоб вам не довелося турбуватися про ці нюанси.


7
Думаю, вся суть питання полягає в тому, щоб це зробити без використання таких рамок.
Санке

147

Попередження: підхід, описаний у цій відповіді, стосується лише дуже конкретних сценаріїв і не є безпечним, оскільки атаки ін'єкцій SQL не залежать лише від можливості ін'єкції X=Y.

Якщо зловмисники намагаються зламати форму через $_GETзмінну PHP або за допомогою рядка запиту URL, ви зможете зловити їх, якщо вони не захищені.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Тому що 1=1, 2=2, 1=2, 2=1, 1+1=2і т.д. ... є загальними питаннями до бази даних SQL зловмисника. Можливо, він також використовується багатьма хакерськими програмами.

Але ви повинні бути обережними, щоб не переписати безпечний запит зі свого сайту. Код, наведений вище, дає вам підказку, переписати чи перенаправити (це залежить від вас), що хакерський рядок динамічного запиту переходить на сторінку, на якій буде зберігатися IP-адреса зловмисника , або ВСЕ ЇХ КОКІ, історія, браузер чи будь-який інший чутливий інформацію, тож ви зможете з ними пізніше розібратися, заборонивши їхній рахунок або зв’язавшись із органами влади.


Що з цим 1-1=0? :)
Rápli András

@ RápliAndrás Якесь ([0-9\-]+)=([0-9]+).
5ervant

127

Існує так багато відповідей для PHP та MySQL , але ось код для PHP та Oracle для запобігання ін'єкції SQL, а також регулярне використання драйверів oci8:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);

Поясніть параметри oci_bind_by_name.
Джаханзеб Аван

127

Хорошою ідеєю є використання об'єктно-реляційного картографа типу Idiorm :

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Це не тільки рятує вас від ін'єкцій SQL, але і від синтаксичних помилок! Він також підтримує колекції моделей з ланцюжком методів для фільтрації або застосування дій до кількох результатів одночасно та декількох з'єднань.


124

Застаріле попередження: у зразковому коді цієї відповіді (як і зразок коду запитання) використовується MySQLрозширення PHP , яке було застаріле в PHP 5.5.0 та повністю видалено в PHP 7.0.0.

Попередження про безпеку : Ця відповідь не відповідає найкращим практикам безпеки. Утеча недостатня для запобігання ін'єкції SQL , використовуйте замість цього підготовлені оператори . Використовуйте описану нижче стратегію на свій страх і ризик. (Також mysql_real_escape_string()було видалено в PHP 7.)

Використання PDO та MYSQLi є хорошою практикою для запобігання ін'єкцій SQL, але якщо ви дійсно хочете працювати з функціями та запитами MySQL, було б краще використовувати

mysql_real_escape_string

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

Існує більше можливостей для запобігання цьому: як ідентифікувати - якщо вхід - це рядок, число, знак чи масив, існує так багато вбудованих функцій, щоб виявити це. Крім того, було б краще використовувати ці функції для перевірки вхідних даних.

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

І набагато краще використовувати ці функції для перевірки вхідних даних mysql_real_escape_string.


10
Крім того, немає сенсу перевіряти учасників масиву $ _POST за допомогою is_string ()
Ваше здорове розуміння

21
УВАГА! mysql_real_escape_string() не є безпогрішним .
eggyal

10
mysql_real_escape_stringтепер застаріла, тому її варіант уже не є життєздатним. Це буде видалено з PHP в майбутньому. Найкраще перейти до того, що рекомендують люди PHP або MySQL.
jww

2
Тема: Не довіряйте наданим користувачам даних. Все, що ви очікуєте - це сміттєві дані зі спеціальними символами або булевою логікою, які самі повинні стати частиною запиту SQL, який ви можете виконувати. Зберігайте значення $ _POST лише як дані, а не частина SQL.
Bimal Poudel

88

Я писав цю маленьку функцію кілька років тому:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

Це дозволяє запускати оператори у однорядковому C # -ish String.Format:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

Він уникає, враховуючи тип змінної. Якщо ви спробуєте параметризувати назви стовпців, стовпців, це не вдасться, оскільки ставить кожну рядок у лапки, що є невірним синтаксисом.

ОНОВЛЕННЯ ЗАБЕЗПЕЧЕННЯ: Попередня str_replaceверсія дозволяла робити ін’єкції, додаючи {#} лексеми в дані користувача. Ця preg_replace_callbackверсія не створює проблем, якщо заміна містить ці лексеми.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.