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


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

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

Що можна зробити, щоб уникнути цього?


2781


походження




Відповіді:


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

У вас, в принципі, є два варіанти досягнення цієї мети:

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

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('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', 'pass');

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

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

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

Хоча ви можете встановити charset у варіантах конструктора важливо зазначити, що "старіші" версії PHP (<5.3.6) безшумно ігнорує параметр кодування в 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(array('column' => $unsafeValue));

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

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

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

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

7953



Просто, щоб додати, тому що я не бачив його де-небудь ще тут, інша лінія захисту є брандмауер веб-програми (WAF), в які можуть бути встановлені правила пошуку атак на ін'єкцію sql: - jkerak
Крім того, офіційна документація mysql_query дозволяє виконувати лише один запит, а також будь-який інший запит; ігнорується Навіть якщо це вже застаріло, існує безліч систем в PHP 5.5.0, які можуть використовувати цю функцію. php.net/manual/en/function.mysql-query.php - Randall Valenciano
Це погана звичка, але це рішення після спроби: не тільки для ін'єкцій SQL, але й для будь-якого типу ін'єкцій (наприклад, для ін'єкцій скважини у F3 framework v2), якщо у вас є готовий старий веб-сайт або програма страждає від дефектів ін'єкцій, одним з варіантів є перепризначення значень вашої supperglobal заздалегідь визначених змін, таких як $ _POST з вичерпними значеннями при завантаженні. За допомогою PDO, все ще можна втекти (також для сьогоднішніх рамок): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1) - Alix
У цьому відповіді не вистачає пояснень того, що є підготовленим твердженням - одне - це хіти, якщо ви використовуєте багато підготовлених повідомлень під час запиту, а іноді це припадає на 10-кратний показ результатів. Кращим випадом буде використання PDO з параметром прив'язки, але підготовка звіту. - donis
Використання PDO краще, якщо ви використовуєте прямий запит, переконайтеся, що ви використовуєте mysqli :: escape_string - Kassem Itani


УВАГА:   Код зразка цього коду (наприклад, зразок коду питання) використовує PHP mysql розширення, яке застаріло в PHP 5.5.0 і повністю вилучено в PHP 7.0.0.

Якщо ви використовуєте нову версію 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 підхід, ви будете страждати від описаної проблеми Поліноміальний в коментарі нижче. Цей випадок є складнішим, оскільки цілі числа не будуть оточені цитатами, тому ви могли б розібратися, підтвердивши, що користувацький вхід містить лише цифри.
  • Існують й інші випадки, про які я не знаю. Ви можете знайти це є корисним ресурсом для деяких найбільш тонких проблем, з якими ви можете зіткнутися.

1514



використовуючи mysql_real_escape_string достатньо, або я також повинен використовувати параметризований? - peiman F.
@peimanF зберегти хорошу практику використання параметризованих запитів, навіть на місцевому проекті. З параметризованими запитами ви знаходитесь гарантовано що SQL ін'єкції не буде. Але майте на увазі, що ви повинні дезинфікувати дані, щоб уникнути помилкового пошуку (наприклад, ін'єкції XSS, наприклад, розміщення HTML-коду в тексті) з htmlentitiesнаприклад - Goufalite
@peimanF Гарна практика параметризованих запитів та зв'язування значень, але справжня рядок втечі на сьогоднішній день хороша - Richard


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

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

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

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

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

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

Однак існує ще один спосіб захистити ідентифікатори - втеча. Поки ви маєте ідентифікатор, процитований, ви можете уникнути дрібниць, подвоївши їх.

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

Отже, щоб коротко розповісти: це а заповнювач, ні підготовлена ​​заява можна розглядати як срібну кулю.

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

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

Оновити

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

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

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

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

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

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

Отже, на відміну від того, що "втікає", готуються заяви є що дійсно захищає від ін'єкцій SQL (коли це застосовується).

Якщо ви все ще не переконані, ось поетапне пояснення, яке я написав Посібник для автошляхів для профілактики ін'єкцій, де я докладно пояснив ці питання і навіть склав розділ, повністю присвячений поганим практикам та їх розкриття.


946



Відмінна, добре продумана стаття. Я міг би додати, що використання фільтрів Sanitize PHP - це тип (але не точно) білий список видів. Наприклад, FILTER_SANITIZE_NUMBER_INT Дозволяє лише кількість символів, тим самим білий список символів, а не цілі рядки. У поєднанні з підготовленими заявами, це робить хороший підхід "пояса та підтяжки". - Sablefoste
@ Sablefoste вам не потрібно білого списку тут. Будь-яка санітарія буде зайвою. Чим менше правил слідує, тим менше помилок ви зробите. Хоча ви можете робити будь-які перевірки, робіть це заради логіки вашої програми, але не для бази даних. - Your Common Sense


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

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

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


770



Не PDO не обертати mysqli для MySQL DB? У цьому випадку, звичайно, він не може бути швидшим, ніж mysqli. Я все ж рекомендую це. Це набагато кращий інтерфейс, який використовує API MySQL. - Peter Bagnall
Використання параметризованих запитів - це те, що прискорює запити. Технічно mysqli може бути навіть швидше з дуже малим запасом. Фактична кількість часу, який сервер приймає, щоб відповісти на запит, затінює будь-яку різницю в часі, який може статися, тому що ви використовуєте обгортку. Але mysqli прив'язаний до бази даних. Якщо ви хочете використовувати інший двигун бази даних, вам доведеться змінити всі виклики, які використовують mysqli. Не так для PDO. - Kibbee
PDO не підтримує динаміку order by на жаль :( - Horse


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

($conn це PDO об'єкт)

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

567



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


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

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

Мій підхід:

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

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

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

  • Цей метод hex часто використовується при передачі двійкових даних, але я не бачу причин, чому б не використовувати його на всіх даних, щоб запобігти атакам SQL injection. Зверніть увагу, що вам слід додати дані до 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, text, block, binary і т. д.
Крім того, його використання трохи складніше, якщо ви збираєтеся вставити порожню рядок. Вам доведеться цілком замінити його '', або ви отримаєте помилку.

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


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

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

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

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

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

SELECT ... WHERE id = -1 об'єднати всі вибрати table_name з information_schema.tables

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

SELECT ... WHERE ідентифікатор = -1 об'єднати все select_name ім'я_файлу з information_schema.column де table_name = 0x61727469636c65

І тоді просто виберіть будь-які дані, які потрібно. Чи не круто?

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


501



@ СумітГупта Так, ти це зробив. MySQL не з'єднується з + але з CONCAT. І до продуктивності: я не думаю, що це впливає на продуктивність, тому що MySQL має проаналізувати дані, і це не має значення, якщо походження є рядком або шістнадцятковим - Zaffy
@YourCommonSense Які помилки ви стикаєтеся? Бути специфічним. - Zaffy
@YourCommonSense Ви не розумієте поняття ... Якщо ви хочете мати рядок у mysql, ви цитуєте це так 'root' або ти можеш це зробити 0x726f6f74 АЛЕ, якщо ви хочете отримати номер і надіслати його як рядок, ви, ймовірно, напишите '42' не CHAR (42) ... '42' у hex буде 0x3432 ні 0x42 - Zaffy
@YourCommonSense я не маю нічого сказати ... просто lol ... Якщо ви все ще хочете спробувати hex на числових полях, перегляньте другий коментар. Я впевнений, що він буде працювати. - Zaffy
@YourCommonSense ви все ще не розумієте? Ви не можете використовувати 0x і concat, тому що якщо рядок порожній, ви закінчите помилку. Якщо вам потрібна проста альтернатива вашому запиту, спробуйте це SELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ') - Zaffy


ВАЖЛИВО

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

Є такі бібліотеки, як Aura.Sql і EasyDB що дозволяє розробникам використовувати підготовлені заяви легше. Щоб дізнатись більше про те, чому підготовлені заяви краще припинення введення SQL, відноситься до це mysql_real_escape_string() обхід і Нещодавно виправлено Unicode 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.


455



Це найкраще, що ви можете зробити із застарілим розширенням mysql. Для нового коду рекомендується перейти на mysqli або PDO. - Álvaro González
Я не згоден з цією "спеціальною функцією, яка запобігає цим атакам". я думаю що mysql_real_escape_string Мета - дозволити створювати правильний запит SQL для кожного вхідного рядка даних. Попередження sql-injection є побічним ефектом цієї функції. - sectus
ви не використовуєте функції для написання правильних вхідних строк даних. Ви просто пишіть правильні, які не потребують втечі або які вже втекли. mysql_real_escape_string () може бути розроблений з ціллю, про яку ви згадуєте, але його єдине значення запобігає ін'єкції. - Nazca
УВАГА!  mysql_real_escape_string()  не непосильний. - eggyal
mysql_real_escape_string тепер застаріла, тому її вже не є життєздатним варіантом. В майбутньому він буде видалений з PHP. Найкраще перейти на те, що рекомендують люди PHP або MySQL. - jww


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

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

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

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


413



Я спробував ваш приклад, і це добре працює для мене. Чи можете ви зрозуміти "це не вирішить кожну проблему" - Chinook
Якщо ви не називаєте рядок, він все ще ін'єктується. Брати $q = "SELECT col FROM tbl WHERE x = $safe_var"; наприклад. Налаштування $safe_var до 1 UNION SELECT password FROM users працює в цьому випадку через відсутність котирувань. Також можна вписати рядки в запит за допомогою CONCAT і CHR. - Polynomial
@Polynomial Повністю правильно, але я бачу це просто як неправильне використання. Поки ви правильно використовуєте його, він обов'язково працюватиме. - glglgl
УВАГА!  mysql_real_escape_string()  не непосильний. - eggyal
mysql_real_escape_string тепер застаріла, тому її вже не є життєздатним варіантом. В майбутньому він буде видалений з PHP. Найкраще перейти на те, що рекомендують люди PHP або MySQL. - jww


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


347



Дійсно; Запуск з magic_quotes включений тільки заохочує погану практику. Однак іноді ви не завжди можете контролювати оточення до цього рівня - або ви не маєте доступу до керування сервером, або ваша програма повинна співіснувати з додатками, які (сором'язові) залежать від такої конфігурації. З цих причин добре писати портативні програми - хоча, очевидно, ці витрати витрачаються, якщо ви контролюєте середовище розгортання, наприклад, тому що це внутрішнє додаток або тільки буде використовуватися у вашому конкретному середовищі. - Rob
Як і з PHP 5.4, мерзотність, відома як "магічні котирування", була убитий мертвий. І добрий занепад до поганого сміття. - BryanH


Параметризований запит і перевірка вводу - це спосіб піти. Існує багато сценаріїв, за якими може відбуватися ін'єкція 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 (при недостатньому вичерпанні)


330



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