Питання Чому я не повинен використовувати функції mysql_ * у PHP?


Які технічні причини, чому не варто користуватися mysql_* функції (наприклад, mysql_query(), mysql_connect() або mysql_real_escape_string())?

Чому я повинен використовувати щось інше, навіть якщо вони працюють на моєму сайті?

Якщо вони не працюють на моєму сайті, то чому я отримую помилки, як

Попередження: mysql_connect (): немає такого файлу чи каталогу


2196
2017-10-12 13:18


походження


Помилка виглядати як: Фатальна помилка: Uncaught Помилка: виклик до undefined функції mysql_connect () ... - Bimal Poudel
Однією лише недостовірною є достатня причина, щоб уникнути їх - Sasa1234


Відповіді:


Розширення MySQL:

  • Не активно розвивається
  • Є офіційно не підтримується від PHP 5.5 (випущений червень 2013 р.).
  • Був вилучено повністю як з PHP 7.0 (випущена в грудні 2015 року)
    • Це означає, що починаючи з 31 грудня 2018 р він не буде існувати в будь-якій підтримуваній версії PHP. В даний час він отримує тільки безпека оновлення
  • Відсутній інтерфейс OO
  • Не підтримує:
    • Неблокірующие, асинхронні запити
    • Підготовлені заяви або параметризовані запити
    • Збережені процедури
    • Кілька заявок
    • Операції
    • "Новий" метод перевірки автентичності паролю (за замовчуванням в MySQL 5.6, необхідний у 5.7)
    • Вся функціональність в MySQL 5.1

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

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

Побачити порівняння розширень SQL.


1814
2018-01-01 11:52



Однією лише недостовірною є достатня причина, щоб уникнути їх. Вони не будуть там один день, і ти не будеш щасливий, якщо ти покладешся на них. Решта - це лише перелік речей, які використовують старі розширення, заважали людям навчатися. - Tim Post♦
Знецінення - це не чарівна куля, кожен, здається, це думає. Сам по собі PHP не буде там один день, але ми покладаємося на інструменти, які ми маємо в наш час сьогодні. Коли ми повинні змінити інструменти, ми будемо. - Lightness Races in Orbit
@LightnessRacesinOrbit - Deprecation - це не чарівна куля, це прапор, який говорить: "Ми визнаємо це смокче, тому ми не будемо підтримувати його набагато довше". Хоча краща майбутня коректура коду є вагомою причиною відходу від застарілих функцій, це не єдиний (або навіть основний). Змінити інструменти, оскільки існують кращі інструменти, а не тому, що ви змушені. (І зміна інструментів, перш ніж ви змушені означає, що ви не вивчаєте нові, тільки тому, що ваш код зупинився і потребує фіксації вчора ... це найгірший час для вивчення нових інструментів). - Quentin
Одна справа, про яку я не бачив, згадав про відсутність підготовлених заяв, - це проблема виконання. Кожного разу, коли ви видаєте заяву, щось повинен скомпілювати його так, щоб демон MySQL це зрозумів. За допомогою цього API, якщо ви видаєте 200 000 одного запиту в циклі, це 200 000 разів, щоб запит було скомпільовано для MySQL, щоб це зрозуміти. З підготовленими твердженнями вона складається один раз, а потім значення параметризуються в складеному SQL. - Goldentoa11
@symcbean, це, безперечно, робить ні Підтримка підготовлених повідомлень. Це насправді є основною причиною, через яку вона застаріла. Без (простий у використанні) підготовлених тверджень, розширення mysql часто стає жертвою атак на SQL injection. - rustyx


PHP пропонує три різні API для підключення до MySQL. Це такі mysql(видалено з PHP 7), mysqli, і PDO розширення

The mysql_* Функції були дуже популярними, але їх використання більше не рекомендується. Команда документації обговорює ситуацію з безпекою бази даних, і це є частиною цього компоненту - освітувати користувачів, щоб відмовитися від звичайного розширення ext / mysql (перевірка php.internals: припинення використання ext / mysql)

А пізніша команда розробників PHP прийняла рішення про створення E_DEPRECATED помилки, коли користувачі підключаються до MySQL, чи через mysql_connect(), mysql_pconnect() або вбудовану функцію неявного з'єднання ext/mysql.

ext/mysql був офіційно не підтримується з версії PHP 5.5 і був видалено з PHP 7.

Дивіться Red Box?

Коли ти йдеш на будь-який mysql_* сторінка посібника функції ви бачите червону скриньку, пояснюючи, що її більше не слід використовувати.

Чому


Переїзд з ext/mysql це не тільки про безпеку, але і про доступ до всіх функцій бази даних MySQL.

ext/mysql був побудований для MySQL 3.23 і з цього часу вийшло дуже мало додатків, в той час як в основному зберігається сумісність із цією старою версією, що робить код трохи складніше підтримувати. Відсутні функції, які не підтримуються ext/mysql включати: (з керівництва PHP)

Причина не використовувати mysql_* функція:

  • Не в активному розвитку
  • Вилучено з PHP 7
  • Відсутній інтерфейс OO
  • Не підтримує неблокірующие, асинхронні запити
  • Не підтримує підготовлені заяви або параметризовані запити
  • Не підтримує збережені процедури
  • Не підтримує декілька висловлювань
  • Не підтримує операції
  • Не підтримує всю функціональність у MySQL 5.1

Над пунктом цитується з відповіді Квентіна

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

Див порівняння розширень SQL.


Придушення запобіжних попереджень

Поки код перетворюється на MySQLi/PDO, E_DEPRECATED помилки можуть бути пригнічені налаштуванням error_reporting в php.ini виключити E_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Зауважте, що це також сховане інші попередження попередження, що, однак, може бути для інших речей, крім MySQL. (з керівництва PHP)

Стаття PDO проти MySQLi: що слід використовувати? по Дежан Мар'янович допоможе вам вибрати.

І це кращий спосіб PDO, і зараз я пишу простий PDO підручник.


Простий та короткий підручник PDO


Q. Перше питання на мій погляд був: що таке 'PDO`?

А. "PDO - об'єкти даних PHP - це рівень доступу до бази даних, що забезпечує єдиний спосіб доступу до декількох баз даних ".

alt text


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

З mysql_* функція або ми можемо сказати це старим способом (не підтримується в PHP 5.5 і вище)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

З PDO: Все що вам потрібно зробити, це створити новий PDO об'єкт Конструктор приймає параметри для визначення джерела бази даних PDOРосійський конструктор в основному приймає чотири параметри, які є DSN (назва джерела даних) та додатково username, password.

Тут я думаю, що ви знайомі з усіма, за винятком DSN; це нове в PDO. А. DSN це в основному рядок параметрів, які розповідають PDO який драйвер для використання, і дані з'єднання. Для отримання додаткової інформації перевірте PDO MySQL DSN.

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

Примітка: ви також можете використовувати charset=UTF-8, але іноді це призводить до помилки, тому його краще використовувати utf8.

Якщо є якась помилка підключення, вона буде викидати PDOException об'єкт, який може бути спійманий для обробки Exception далі

Добре прочитай: Підключення та управління підключенням ¶ 

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

Інший - вимкнути підготовку емуляції, яка увімкнена в MySQLза замовчуванням драйвер, але приготувати емуляцію слід вимкнути для використання PDO безпечно

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

Він доступний лише для старої версії MySQL які я не рекомендую.

Нижче наведено приклад того, як ви можете це зробити:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Чи можемо ми встановити атрибути після будівництва PDO?

Так, ми також можемо встановити деякі атрибути після будівництва PDO з setAttribute спосіб:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Обробка помилок


Обробка помилок набагато легше PDO ніж mysql_*.

Звичайна практика при використанні mysql_* це:

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die() це не хороший спосіб керувати помилкою, оскільки ми не можемо впоратися з цією справою die. Це просто закінчить скрипт, а потім повторює помилку на екрані, який зазвичай НЕ хоче показати своїм кінцевим користувачам, і дозволити кривавим хакерам знайти вашу схему. З іншого боку, повертаються значення mysql_* функції можуть бути використані разом з mysql_error () для обробки помилок.

PDO пропонує краще рішення: винятки. Все що ми робимо PDO повинна бути загорнута в a try-catch блок Ми можемо змусити PDO в один з трьох режимів помилки, встановивши атрибут режиму помилки. Нижче наведено три режими обробки помилок.

  • PDO::ERRMODE_SILENT. Це просто встановлення кодів помилок і діє майже так само, як і mysql_* де ви повинні перевірити кожен результат, а потім подивитися на $db->errorInfo(); щоб отримати інформацію про помилку.
  • PDO::ERRMODE_WARNING Підніміть E_WARNING. (Попередження про виконання часу (не фатальні помилки). Виконання сценарію не призупинено.)
  • PDO::ERRMODE_EXCEPTION: Викиньте виключення. Він являє собою помилку, зроблену PDO. Ви не повинні кидати PDOException з вашого власного коду. Побачити Винятки для отримання додаткової інформації про винятки в PHP. Він дуже сильно подібний or die(mysql_error());, коли його не спіймають. Але на відміну від or die(), the PDOException можуть бути сприйняті та оброблені витончено, якщо ви вирішите це зробити.

Добре прочитай:

Люблю:

$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

І ви можете його загорнути try-catch, як показано нижче:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) {
    echo "An Error occured!"; //User friendly message/message you want to show to user
    some_logging_function($ex->getMessage());
}

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

function data_fun($db) {
    $stmt = $db->query("SELECT * FROM table");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db);
}
catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

Також ви можете обробляти за допомогою or die() або ми можемо сказати як mysql_*, але це буде дуже різноманітно. Ви можете приховати небезпечні повідомлення про помилки у виробництві, повернувшись display_errors off і просто читає журнал помилок.

Тепер, прочитавши всі перераховані вище питання, ви, напевно, думаєте: що таке, коли я просто хочу почати нахилятись просто SELECT, INSERT, UPDATE, або DELETE заяви Не хвилюйся, тут ми йдемо:


Вибір даних

PDO select image

Так що ти робиш mysql_* це:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());

$num_rows = mysql_num_rows($result);

while($row = mysql_fetch_assoc($result)) {
    echo $row['field1'];
}

Зараз в PDO, ви можете зробити це, як:

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

Or

<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

//Use $results

Примітка: Якщо ви використовуєте метод, як показано нижче (query()), цей метод повертає a PDOStatement об'єкт Тому, якщо ви хочете отримати результат, скористайтеся ним, як описано вище.

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

У PDO Data він отримується через ->fetch(), метод обробки вашого твердження. Перш ніж надсилати виклик, кращий підхід буде означати PDO, як ви хочете отримувати дані. У цьому розділі я поясню це.

Режими завантаження

Зверніть увагу на використання PDO::FETCH_ASSOC в fetch() і fetchAll() код вище. Це розповідає PDO щоб повертати рядки як асоціативний масив з назвами полів, як ключі. Існує також багато інших режимів завантаження, які я буду пояснювати один за іншим.

Перш за все, я розповідаю, як вибрати режим завантаження:

 $stmt->fetch(PDO::FETCH_ASSOC)

У вищенаведеному я використовував fetch(). Ви також можете використовувати:

  • PDOStatement::fetchAll() - Повертає масив, що містить всі рядки результуючого набору
  • PDOStatement::fetchColumn() - Повертає один стовпець з наступного рядка набору результатів
  • PDOStatement::fetchObject() - Завантажує наступний рядок і повертає його як об'єкт.
  • PDOStatement::setFetchMode() - Встановити режим завантаження за замовчуванням для цього твердження

Тепер я приходжу до режиму завантаження:

  • PDO::FETCH_ASSOC: повертає масив, індексований за назвою стовпця як повернений у вашому наборі результатів
  • PDO::FETCH_BOTH (за умовчанням): повертає масив, індексований обома іменами стовпця та 0-індексованим номером стовпця як повернутим у вашому наборі результатів

Є ще більше варіантів! Прочитайте про них усе PDOStatement Завантажте документацію..

Отримання кількості рядків:

Замість того, щоб використовувати mysql_num_rows щоб отримати кількість повернутих рядків, ви можете отримати a PDOStatement і зроби rowCount(), люблю:

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

Отримання останнього вставленого ідентифікатора

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

Вставити та оновити або видалити заяви

Insert and update PDO image

Що ми робимо mysql_* функція:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

І в pdo це те ж саме можна виконати:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

У вищезазначеному запиті PDO::exec виконайте оператор SQL і повертає кількість уражених рядків.

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

Вищевказаний метод корисний лише тоді, коли ви не використовуєте змінну в запиті. Але коли вам потрібно використати змінну в запиті, ніколи не спробуйте, як описано вище, і там підготовлена ​​заява або параметризована заява є


Підготовлені заяви

Q. Що таке підготовлена ​​заява та чому мені це потрібно?
А. Підготовлена ​​заява - це попередньо скомпільований оператор SQL, який може бути виконаний кілька разів, відправляючи лише дані на сервер.

Типовий робочий процес використання підготовленої заяви виглядає наступним чином (цитата з Вікіпедії три 3 бали):

  1. Підготуйте: Шаблон звіту створюється програмою та надсилається в систему управління базами даних (СУБД). Деякі значення залишаються невизначеними, називаються параметрами, заповнювачами або змінних зв'язків (позначені як ? нижче):

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. СУБД аналізує, компілює та виконує оптимізацію запитів у шаблоні звітів і зберігає результат без його виконання.

  3. Виконати: Пізніше додаток постачає (або зв'язує) значення для параметрів, а СУБД виконує оператор (можливо повертаючи результат). Програма може виконувати оператор стільки разів, скільки хоче, з різними значеннями. У цьому прикладі він може поставити "Хліб" для першого параметра і 1.00для другого параметра.

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

Q. Отже, що ж назву заповнювачів і як я їх використовувати?
А. Іменовані заповнювачі. Використовуйте описові назви, перед якими перетинається двокрапка, а не знаки питання. Ми не піклуємося про позицію / порядок вартості у власнику імені місця:

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

Ви також можете пов'язати, використовуючи масив виконання:

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

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

class person {
    public $name;
    public $add;
    function __construct($a,$b) {
        $this->name = $a;
        $this->add = $b;
    }

}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

Q. Отже, що таке неназвані заповнювачі та як я можу їх використовувати?
А. Давайте мати приклад:

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

і

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));

У вищенаведеному ви можете побачити ці ? замість імені, як у власнику імені. Тепер у першому прикладі ми призначаємо змінні для різних заповнювачів ($stmt->bindValue(1, $name, PDO::PARAM_STR);) Потім ми призначаємо значення тим заповнювачам і виконуємо твердження. У другому прикладі перший елемент масиву переходить до першого ? а другий - другий ?.

ПРИМІТКА: В неназвані заповнювачі ми повинні подбати про правильний порядок елементів в масиві, що ми переходимо до PDOStatement::execute() метод


SELECT, INSERT, UPDATE, DELETE підготовлені запити

  1. SELECT:

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id));
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
  2. INSERT:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    
  3. DELETE:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
    
  4. UPDATE:

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();
    

ПРИМІТКА:

Однак PDO і / або MySQLi не є повністю безпечними. Перевірте відповідь Чи PDO підготовлені заяви достатньо, щоб запобігти ін'єкції SQL? по ircmaxell. Крім того, я навожу частку з його відповіді:

$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(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));

1163
2017-10-12 13:28



Про що слід ознайомитись з прочитаним вище, наведене вище: підготовлена ​​заява забирає будь-яке змістовне використання IN (...) construct. - Eugen Rieck
@ Аміна, ні, це не так! :] Хоча NullPoiитея дійсно зробив чудову роботу з його написання, це, безумовно, не хороше читання, бо це шлях до довгого часу. Я впевнений, що 8 із 10 відвідувачів просто пропустять його. І ви також маєте пояснення, чому ця відповідь не є найкращою. А. tl;dr Частина на початку буде гарна ідея, я думаю. - trejder
Питання полягало в тому, "чому я не повинен використовувати функції mysql_ * в PHP". Ця відповідь, в той час як вражаюча та повна корисної інформації, виходить за межі можливостей, і, як каже @trejder - 8 з 10 людей збираються пропустити цю інформацію, просто тому, що не мають 4 годин, щоб витрачати, намагаючись працювати це Це було б набагато більш цінно розбите і використане як відповіді на кілька, більш точних питань. - Alex McMillan
Персоанлі я віддаю перевагу mysqli і PDO. Але для умирання, я спробував виключити альтернативу function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx(); Це працює для виключення винятків. - kuldeep.kamboj


По-перше, давайте почнемо зі стандартного коментарю, який ми даємо кожному:

Будь ласка, не використовуйте mysql_* функції в новому коді. Вони більше не підтримуються і офіційно застаріли. Див червона скринька? Дізнатися про підготовлені заяви замість цього і використовувати PDO або MySQLi - Ця стаття допоможе тобі вирішити, яка. Якщо ви виберете PDO, ось хороший підручник.

Давайте пройдемо це, вирок із пропозицією, і пояснимо:

  • Вони більше не підтримуються і офіційно застаріли

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

    НОВИНКА! - ext / mysql зараз офіційно не підтримується з PHP 5.5!

    Новіше! ext / mysql був видалений в PHP 7.

  • Замість цього ви повинні дізнатися про підготовлені заяви

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

    Для отримання додаткової інформації див Як я можу запобігти ін'єкції SQL у PHP?

  • Дивіться Red Box?

    Коли ви йдете до будь-якого mysql сторінка посібника функції ви бачите червону скриньку, пояснюючи, що її більше не слід використовувати.

  • Використовуйте або PDO, або MySQLi

    Є кращі, надійніші та надійні альтернативи, PDO - об'єкт бази даних PHP, який пропонує повний підхід OOP до взаємодії бази даних і MySQLi, що є специфічним удосконаленням MySQL.


280
2017-12-24 23:30



Є ще одна річ: я думаю, що функція все ще існує в PHP лише з однієї причини - сумісність зі старою, застарілою, але все ще працюючою CMS, електронною комерцією, системами дошки оголошень тощо. Нарешті, вона буде видалена, і вам доведеться переписати ваш додаток ... - Kamil
@ Каміль: Це правда, але це не дійсно причина, чому ви не повинні її використовувати. Причина не використовувати її тому, що вона стара, незахищена тощо. :) - Madara Uchiha♦
@Mario - PHP-девелопери мають процес, і вони просто проголосували за офіційне припинення використання ext / mysql на рівні 5.5. Це вже не гіпотетичне питання. - SDC
Додавання пари додаткових ліній із перевіреною технікою, такими як PDO або MySQLi, як і раніше, забезпечує простоту використання, яку завжди пропонував PHP. Я сподіваюсь, що заради розробника він / вона знає, що бачачи ці божевільні функції mysql_ * в будь-якому навчальному посібнику, насправді не дотримується уроку, і повинен сказати ОП, що цей тип коду був 10 років тому - і слід поставити під сумнів релевантність підручника теж! - FredTheWebGuy
Яку відповідь слід згадати: підготовлена ​​заява забирає будь-яке змістовне використання IN (...) construct. - Eugen Rieck


Простота використання

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

Сучасні API баз даних - це просто легше використовувати.

В основному це пов'язані параметри який може спростити код. І з відмінні підручники (як показано вище) перехід до PDO не надто важкий.

Переписування більшої кодової бази одночасно займає багато часу. Raison d'être для цієї проміжної альтернативи:

Еквівалентні функції pdo_ * замість mysql_ *

Використовуючи <pdo_mysql.php> ви можете переключитися зі старих функцій mysql_ з мінімальні зусилля. Це додає pdo_ функціональні обгортки, які замінюють їх mysql_ колеги

  1. Просто include_once("pdo_mysql.php"); в кожному сценарії викликів, який повинен взаємодіяти з базою даних.

  2. Видаліть mysql_ префікс функції скрізь і замінити його pdo_.

    • mysql_connect() стає pdo_connect()
    • mysql_query() стає pdo_query()
    • mysql_num_rows() стає pdo_num_rows()
    • mysql_insert_id() стає pdo_insert_id()
    • mysql_fetch_array() стає pdo_fetch_array()
    • mysql_fetch_assoc() стає pdo_fetch_assoc()
    • mysql_real_escape_string() стає pdo_real_escape_string()
    • і так далі... 

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

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }
    

Et voilà.
Ваш код є використовуючи PDO
Настав час насправді використовувати це

Зв'язані параметри можуть бути прості у використанні

Вам просто потрібен менш громіздкий API.

pdo_query() додає дуже зручну підтримку пов'язаних параметрів. Перетворення старого коду є простим:

Перемістіть ваші змінні в рядок SQL.

  • Додайте їх як параметри функції, розділені комами pdo_query().
  • Розмістіть знаки питання ? як заповнювачі, коли змінні були раніше.
  • Позбуватися ' одиночні лапки, які раніше містять рядкові значення / змінні.

Перевага стає більш очевидною для довшого коду.

Часто буквенні змінні не просто інтерполюються в SQL, але об'єднуються з викликами, що виключаються між ними.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

З ? Застосовувані заповнювачі вам не потрібно турбуватися з цим:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

Пам'ятайте, що pdo_ * все ще дозволяє або.
Просто не виключайте змінну і зв'яжіть його за тим самим запитом.

  • Функція заповнювача забезпечується справжньою PDO позаду.
  • При цьому також дозволено :named Заповнювач списків пізніше.

Що більш важливо, ви можете безпечно передати змінні $ _REQUEST [] за будь-яким запитом. Коли подано <form> поля збігаються з структурою бази даних, точно вона ще коротша:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

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

Виправте або видаліть будь-яку стару школу sanitize() функція

Як тільки ви перетворили все mysql_ дзвінки до pdo_query з обов'язковими параметрами видаліть усі зайві pdo_real_escape_string дзвінки

Зокрема ви повинні виправити будь-який sanitize або clean або filterThis або clean_data функції, що рекламуються даними підручниками в одній формі або іншому:

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

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

  • Правильне замовлення було б: недійсним stripslashes як найглибший виклик, потім trim, потім strip_tags, htmlentities для вихідного контексту, і лише остаточно _escape_string оскільки його додаток має безпосередньо передувати інтерпроцедурі SQL.

  • Але першим кроком просто позбутися від _real_escape_string дзвонити

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

  • Обробка рядка / значення делегована PDO та його параметризованим твердженням.

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

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

    Оригінальна реалізація в PHP2 / FI представила її явно з просто "Цитати автоматично виключаються, що полегшує передачу даних форми безпосередньо до запитів msql"Зверніть увагу на те, що це було безпечно для використання з mSQL, оскільки підтримується тільки ASCII.
      Потім PHP3 / Zend повторно ввів magic_quotes для MySQL і неправильно його документував. Але спочатку це було просто а зручність функції, не мають наміру на безпеку.

Як підготовлені заяви відрізняються

Коли ви скрембуєте змінні рядків у запити SQL, це не просто складніше для вас. Це також сторонні зусилля MySQL, щоб знову розділити код та дані.

Ін'єкції SQL просто колись дані кровоточать у код контекст Сервер бази даних не може пізніше визначити, де PHP спочатку скріпив змінні між запитами.

За допомогою пов'язаних параметрів ви розділяєте SQL-код та значення SQL-контексту у вашому PHP-коді. Але це не перемішується знову за кадром (крім PDO :: EMULATE_PREPARES). Ваша база даних отримує незмінні команди SQL і значення змінної 1: 1.

Хоча ця відповідь підкреслює, що вам варто дбати про переваги читання, що виникають при падінні mysql_. Іноді також є перевага продуктивності (повторювані INSERT з різними значеннями) через це видиме та технічне розділення даних / кодів.

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

Використання гібридного PDO

Ці pdo_* Функції обгортки створюють API-інтерфейс із затримкою. (Це дуже що MYSQLI міг би бути, якщо це було не для підсистеми підсистем ідіосинкратичної функції). Вони також виявляють реальну PDO в більшості випадків.
Перезапис не потрібно припиняти використання нових імен функцій pdo_. Ви можете один за одним перевести кожен pdo_query () в звичайний виклик $ pdo-> prepare () -> execute ().

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

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

Можна замінити лише на одну ітерацію:

foreach ($result as $row) {

Або ще краще, прямий і повний пошук масивів:

$result->fetchAll();

У більшості випадків ви отримаєте корисніші попередження, ніж PDO або mysql_, які зазвичай надаються після невдалих запитів.

Інші варіанти

Так що ми сподіваємось візуалізуємо деяких практичний причин і вартий шляху падіння mysql_.

Просто переключитися на  не зовсім вирізає його. pdo_query() це також просто інтерфейс на нього.

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

Незважаючи на те, що вона відповідає найпростішій категорії-це-можливо-можливо-робота, це теж дуже експериментальний код. Я просто написав це на вихідні. Проте є безліч альтернатив. Просто google для Абстракція бази даних PHP і перегляньте трохи. Завжди було і буде багато чудових бібліотек для таких завдань.

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


201
2017-10-12 13:22



Будьте обережні з pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST); функція - тобто: pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true'); - rickyduck
@Tom Звичайно, хоча це не підтримується значно (0.9.2 був останнім), ви можете створити a іскопаемый рахунок, додати до вікі або файл a повідомлення про помилку (без реєстрації IIRC). - mario


The mysql_ функції:

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

136
2018-01-01 17:42



№ 2 однаково справедливо для mysqli_ - eggyal
щоб бути справедливим, враховуючи варіації в діалекті SQL, навіть PDO не дає вам # 2 будь-яку ступінь впевненості. Для цього потрібна правильна обгортка ORM. - SDC
@ SDC дійсно - проблема зі стандартами полягає в тому, що є так багато з них... - Alnitak
xkcd.com/927 - Lightness Races in Orbit
в mysql_* Функція - це оболонка в функціях mysqlnd для нових версій PHP. Таким чином, навіть якщо стара клієнтська бібліотека більше не підтримується, mysqlnd підтримується :) - hakre


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

  • неблокірующие, асинхронні запити
  • збережені процедури, що повертають декілька результатів
  • Шифрування (SSL)
  • Стиснення

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

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

  • Подальше використання цих функцій за допомогою сучасних версій PHP призведе до появи попередніх повідомлень. Їх просто можна вимкнути.
  • в далекому майбутньому вони можуть бути вилучені з PHP за замовчуванням. Також не велика справа, оскільки mydsql ext буде перенесено до PECL, і кожен хостер буде радий скомпонувати PHP, оскільки вони не хочуть втрачати клієнтів, чиї сайти працювали десятиліттями.
  • сильний опір зі спільноти Stackoverflow. Щойно ви згадуєте ці чесні функції, вам кажуть, що вони перебувають під суворим табу.
  • будучи середнім користувачем PHP, швидше за все, ваша ідея щодо використання цих функцій є схильною до помилок та неправильною. Просто через всі ці численні підручники та навчальні посібники, які навчають вас неправильним шляхом. Не самі функції - я повинен це підкреслити - але спосіб їх використання.

Це останнє питання є проблемою.
Але, на мій погляд, запропоноване рішення також не є кращим.
Мені здається занадто ідеалістичний мрію, що всі ці користувачі PHP навчаться правильно обробляти SQL запити одночасно. Швидше за все, вони просто змінять mysql_ * на mysqli_ * механічно, залишаючи підхід таким же. Особливо тому, що mysqli робить підготовлені заяви використанням неймовірним болісним та тривожним.
Не кажучи вже про це рідний підготовлені заяви недостатньо для захисту від SQL injektions, і ні mysqli, ні PDO не пропонує рішення.

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

Крім того, є деякі помилкові чи несуттєві причини, наприклад

  • Не підтримує збережені процедури (ми використовували mysql_query("CALL my_proc"); на століття)
  • Не підтримує Операції (таку саму, як зазначено вище)
  • Не підтримує декілька повідомлень (хто їх потребує?)
  • Не активно розвивається (так, що це впливає ви будь-яким практичним способом?)
  • Не вистачає інтерфейсу OO (для створення декількох годин)
  • Не підтримує Підготовлені виписки або Параметризовані запити

Останній цікавий момент. Хоча mysql ext не підтримує рідний підготовлені заяви, вони не є обов'язковими для забезпечення безпеки. Ми можемо легко підробити підготовлені заяви з використанням ручних заповнювачів (як і PDO):

function paraQuery()
{
    $args  = func_get_args();
    $query = array_shift($args);
    $query = str_replace("%s","'%s'",$query); 

    foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val);
    }

    $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

вуаля, все параметризовано і безпечно.

Але добре, якщо вам не подобається червона скринька в посібнику, виникає проблема вибору: mysqli або PDO?

Ну, відповідь буде таким:

  • Якщо ви розумієте необхідність використання a абстракція шару бази даних і шукає API для створення одного, mysqli це дуже хороший вибір, оскільки він дійсно підтримує багато специфічних для mysql функцій.
  • Якщо, як і більшість людей PHP, ви використовуєте необроблені API-виклики прямо в коді програми (що, по суті, є неправильною практикою) - PDO є єдиним вибором, оскільки це подовження припускає, що це не просто API, а досить напів-DAL, все ще неповне, але містить багато важливих функцій, причому два з них робить КПК критично відмінні від mysqli:

    • На відміну від mysqli, PDO може зв'язувати заповнювачів за вартістю, що робить динамічно побудовані запити здійсненними без декількох екранів зовсім брудного коду.
    • на відміну від mysqli, PDO завжди може повернути результат запиту в звичайний масив, тоді як mysqli може робити це лише в установках mysqlnd.

Отже, якщо ви є середнім користувачем PHP і хочете заощадити себе на собі тонну головного болю при використанні нативних підготовлених повідомлень, то єдиний вибір - PDO - знову.
Проте ПДО також не є срібною кулею і має свої труднощі.
Таким чином, я написав рішення для всіх загальних пасток і складних випадків в PDO тег вікі

Тим не менше, всі, хто розмовляє про розширення, завжди пропускають 2 важливі факти про Mysqli і PDO:

  1. Підготовлена ​​заява це не срібна куля. Є динамічні ідентифікатори, які не можуть бути пов'язані з використанням підготовлених повідомлень. Є динамічні запити з невідомим числом параметрів, що робить запит складним завданням.

  2. У коді програми не повинно з'являтися функції mysqli_ * чи PDO.
    Там повинно бути абстрактний шар між ними та кодом додатків, що зробить всю брудну роботу з переплетення, циклічність, обробку помилок тощо, що робить код програми сухим та чистим. Спеціально для складних випадків, таких як створення динамічного запиту.

Отже, просто переключитися на PDO або mysqli недостатньо. Потрібно використовувати ORM, або конструктор запитів, або будь-який інший клас абстракції баз даних замість виклику необроблених функцій API у своєму коді.
І навпаки - якщо у вас є абстрактний шар між вашим кодом додатка та API mysql - насправді не має значення, який двигун використовується. Ви можете використовувати mysql ext, поки воно не стане прийнятним, а потім легко переписати ваш абстрактний клас на інший движок, маючи всі коди додатку недоторканими.

Ось декілька прикладів на моєму клас safemysql щоб показати, як такий клас абстракції повинен бути:

$city_ids = array(1,2,3);
$cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

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

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

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

Інший приклад:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

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

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


99
2017-10-12 13:23



mysql_* робить вразливі місця дуже легко. Оскільки PHP використовується багатьма користувачами-початківцями, mysql_* на практиці активно шкідливий, навіть якщо в теорії його можна використовувати без сучка. - Madara Uchiha♦
everything is parameterized and safe - це може бути параметризовано, але ваша функція не використовується реальний підготовлені заяви. - uınbɐɥs
Як Not under active development лише для цього "0.01%"? Якщо ви створите щось із цією функцією stand-by, оновіть свою mysql-версію за рік і закінчуйте не працюючим системою, то я впевнений, що раптом у цьому "0.01%" дуже багато людей. Я б сказав це deprecated і not under active development тісно пов'язані між собою. Ви можете сказати, що для неї "немає [гідної] причини", але факт полягає в тому, що коли пропонується вибір між варіантами, no active development майже так само погано deprecated Я б сказав? - Nanne
@MadaraUchiha: Ви можете пояснити, наскільки вразливими є дуже легко? Особливо в тих випадках, коли ті ж уразливості не впливають на PDO або MySQLi ... Тому що я не знаю про один, про який ви говорите. - ircmaxell
@ShaquinTrifonoff: звичайно, він не використовує підготовлені заяви. Але також PDO немає, який більшість людей рекомендує над MySQLi. Тому я не впевнений, що це має значний вплив. Наведений вище код (з трохи більше синтаксичним аналізом) - те, що робить PDO, коли ви підготуєте виписку за замовчуванням ... - ircmaxell


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

При використанні mysql_* функції, ви повинні пам'ятати, щоб запускати параметри, що надаються користувачем, через mysql_real_escape_string(). Якщо ви забудете лише одне місце або якщо ви втечете лише частину входу, ваша база даних може бути об'єктом атаки.

Використовуючи підготовлені заяви в PDO або mysqli зробить це таким чином, що подібні помилки програмування складніші.


88
2017-10-12 13:24



На жаль, недостатня підтримка MySQLi_ * для передачі змінної кількості параметрів (наприклад, коли ви хочете передати список значень для перевірки в IN clause) заохочує не використовувати параметри, заохочуючи використання точно тих самих об'єднаних запитів, які залишити MySQL_ * дзвінки уразливими. - Kickstart
Але, знову ж таки, незахищеність не є невід'ємною проблемою функцій mysql_ *, а проблема неправомірного використання. - Agamemnus
@Agamemnus Проблема полягає в тому, що mysql_ * полегшує реалізацію цього "некоректного використання", особливо для недосвідчених програмістів. Бібліотеки, які реалізують підготовлені заяви, ускладнюють створення такого типу помилок. - Trott


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

Як приклад, хтось може використати "enhzflep); drop table users" як ім'я користувача Старі функції дозволять виконувати декілька тверджень за запитом, тому щось подібне до того, що неприємний баггер може видалити всю таблицю.

Якщо потрібно використовувати PDO з mysqli, ім'я користувача буде в кінцевому підсумку "enhzflep); drop table users".

Побачити bobby-tables.com.


71
2017-09-18 12:28



The old functions will allow executing of multiple statements per query - ні, вони не будуть. Цей вид ін'єкцій неможливий за допомогою ext / mysql - єдиний спосіб, яким цей тип ін'єкції можливий з PHP, і MySQL, коли використовується MySQLi та mysqli_multi_query() функція Тип ін'єкції, який можливо з ext / mysql і unescaped рядки, це такі речі, як ' OR '1' = '1 щоб витягувати дані з бази даних, яка не мала бути доступною. У певних ситуаціях можна вводити під-запити, однак до цього часу неможливо змінити базу даних. - DaveRandom


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

По-перше, будь ласка, не соромтеся створити цю тестову базу даних mysql (я назвав мою підготовку):

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

З цією метою ми можемо перейти до нашого PHP-коду.

Давайте припустимо, що наступний сценарій - це процес перевірки адміністратора на веб-сайті (спрощений, але працюючий, якщо ви копіюєте та використовуєте його для тестування):

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }

    $database='prep';
    $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database");

    $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>";
    $result=mysql_query($sql);
    $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
        $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }
    mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Здається досить легітим на перший погляд.

Користувач повинен ввести логін і пароль, чи не так?

Брильянт, не вводьте наступне:

user: bob
pass: somePass

і подайте це.

Вихід є таким:

You could not be verified. Please try again...

Супер! Працюючи, як очікувалося, тепер спробуйте скопіювати ім'я користувача та пароль:

user: Fluffeh
pass: mypass

Дивовижний! Привіт усім п'яти, код правильно підтверджено адміністратором. Це прекрасно!

Ну, не дуже. Скажемо, що користувач - це розумна маленька людина. Скажемо, що це людина.

Введіть наступне:

user: bob
pass: n' or 1=1 or 'm=m

І висновок:

The check passed. We have a verified admin!

Вітаю, ви просто дозволили мені ввести лише розділ суперзахищених адміністраторів зі мною, ввівши помилкове ім'я користувача та фальшивий пароль. Серйозно, якщо ви не вірите мені, створіть базу даних з вказаним мною кодом і запустіть цей PHP-код, який, здається, дійсно дійсно перевіряє ім'я користувача та пароль.

Отже, у відповідь, ЧОМУ ВИ ВІДПОВІДАЄТЬСЯ.

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

select id, userid, pass from users where userid='$user' and pass='$pass'

Це запит, але коли ми замінюємо змінні на фактичні входи, які ми використовували, ми отримуємо таке:

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

Подивіться, як я створив свій "пароль", щоб спочатку закрити єдину цитату навколо пароля, а потім ввести абсолютно нове порівняння? Тоді просто для безпеки, я додав іншу "рядок", так що одна цитата буде закрита, як очікується, в коді, який ми спочатку мали.

Проте це не про те, що люди зараз кричать на вас, це про те, як показати вам, як зробити ваш код більш безпечним.

Гаразд, так, що пішло не так, і як ми можемо це виправити?

Це класична атака SQL injection. Один з найпростіших у цьому питанні. За масштабами векторів атаки це малюк, який атакує танк - і виграє.

Отже, як ми захищаємо ваш священний розділ адміністратора, щоб зробити його приємним та безпечним? Перше, що потрібно зробити, - це припинення використання тих, хто дійсно старий та не підтримує mysql_* функції Я знаю, ви дотримувались навчального посібника, який ви знайшли в Інтернеті, і це працює, але це стара, застаріла і впродовж кількох хвилин я просто зламав його, не розгубивши поті.

Тепер у вас є кращі варіанти використання mysqli_ або PDO. Я особисто великий шанувальник PDO, тому я буду використовувати PDO в іншій частині цієї відповіді. Є pro і con, але особисто я вважаю, що Pro набагато переважає Con. Він портативний у кількох движках бази даних, незалежно від того, чи використовуєте ви MySQL або Oracle, чи просто щось криваве - просто змінюючи рядок з'єднання, у нього є всі фантазії, які ми хочемо використовувати, і це приємно і чисто. Мені подобається чистити

Тепер давайте подивимось на цей код ще раз, на цей раз написано з використанням об'єкта PDO:

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }
    $isAdmin=false;

    $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
            $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Головними відмінностями є те, що більше немає mysql_* функції Все це зроблено через об'єкт PDO, по-друге, він використовує підготовлений виклад. Тепер, що таке попереднє твердження, яке ви запитуєте? Це спосіб повідомити базі даних перед виконанням запиту, який запит ми збираємося запустити. У цьому випадку ми повідомляємо базі даних: "Привіт, я збираюся запустити виписку, що вимагає ідентифікатора, userid і передавати від користувачів таблиці, де userid - це змінна, а пропуск є також змінною".

Потім, у операторі exec, ми передаємо базі даних масив з усіма змінами, які він зараз очікує.

Результати фантастичні. Давайте спробуємо спочатку використовувати комбінації імені користувача та пароля:

user: bob
pass: somePass

Користувач не підтверджено. Чудово.

Як на рахунок:

user: Fluffeh
pass: mypass

О, я просто трохи збудився, це спрацювало: чек пройшов. У нас є підтверджений адміністратор!

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

user: bob
pass: n' or 1=1 or 'm=m

На цей раз ми отримуємо таке:

You could not be verified. Please try again...

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

Нарешті, це не означає, що це ПРАВИЛЬНИЙ код. Є багато інших речей, які ви могли б зробити, щоб покращити його, наприклад, використовувати хеш-паролі, переконайтеся, що при зберіганні сенсорної інформації в базі даних ви не зберігаєте його в звичайному тексті, не маєте декілька рівнів перевірки, але, дійсно, якщо ви просто зміните свій старий код, що піддається ін'єкції, до цього, вам буде добре, як писати хороший код - і той факт, що ви отримали це далеко і все ще читаєте, дає мені сподівання, що ви не тільки реалізуєте цей тип коду при написанні ваших веб-сайтів та програм, але ви можете піти і досліджувати ті інші речі, про які я щойно згадав - і багато іншого. Напишіть найкращий код, який ви можете, а не основний код, який практично не працює.


63
2017-09-02 07:20



Спасибі за вашу відповідь! Мати мою +1! Варто це зауважити mysql_* в Росії не є небезпечним, але він сприяє небезпечному коду через погані підручники та відсутність належного викладу підготовки API. - Madara Uchiha♦
розпущені паролі, ой жах! = oP Інакше +1 для детального пояснення. - cryptic ツ