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


2502

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

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

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

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


Помилка схожа на: Фатальна помилка: Невиконана помилка: Виклик невизначеної функції mysql_connect () ...
Bimal Poudel

21
Сам застарілий є достатньою причиною, щоб їх уникнути
Sasa1234

Відповіді:


2088

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

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

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

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

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


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

111
Знечарування - це не чарівна куля, яка, здається, вважає, що це є. Сам PHP одного дня не буде, але ми покладаємось на інструменти, які ми маємо сьогодні в розпорядженні. Коли нам доведеться міняти інструменти, ми будемо.
Гонки легкості на орбіті

133
@LightnessRacesinOrbit - Депресія - це не чарівна куля, це прапор, який говорить: "Ми визнаємо це відстійно, тому не будемо підтримувати його довше". Хоча краща перевірка коду в майбутньому є вагомою причиною відійти від застарілих функцій, він не єдиний (або навіть основний). Змінюйте інструменти, тому що є кращі інструменти, а не тому, що вас змушують. (І зміна інструментів перед тим, як ви змушені означати, що ви не вивчаєте нові лише тому, що ваш код перестав працювати і потребує виправлення вчора ... це найгірший час для вивчення нових інструментів).
Квентін

18
Одне, що я не бачив згадувати про відсутність підготовлених заяв, - це питання ефективності. Кожен раз, коли ви видаєте заяву, щось доводиться компілювати, щоб демон MySQL міг це зрозуміти. За допомогою цього API, якщо ви видаєте 200000 одного і того ж запиту в циклі, це 200 000 разів, щоб запит було складено для MySQL, щоб зрозуміти його. З підготовленими операторами вона компілюється один раз, а потім значення параметризуються в компільований SQL.
Goldentoa11

20
@symcbean, Він, безумовно, не підтримує підготовлені заяви. Це насправді основна причина, чому вона застаріла. Без (простий у використанні) підготовлених висловлювань розширення mysql часто стає жертвою атак ін'єкції SQL.
rustyx

1287

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

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

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

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

Бачите Червону скриньку?

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

Чому?


Відійти від ext/mysqlне лише щодо безпеки, але й щодо доступу до всіх можливостей бази даних MySQL.

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

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

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

Над точкою, цитованою з відповіді Квентіна

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

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


Придушення попереджень про депресію

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

error_reporting = E_ALL ^ E_DEPRECATED

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

Стаття PDO проти MySQLi: Що слід використовувати? автор Dejan Marjanovic допоможе вам вибрати.

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


Простий та короткий навчальний посібник із PDO


Питання: Першим моїм запитанням було: що таке "PDO"?

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

alt текст


Підключення до 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«s конструктора в основному приймає чотири параметри , які DSN(ім'я джерела даних) і , можливо username, password.

Тут я думаю, що ви знайомі з усім, крім DSN; це нове в PDO. A 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повинні бути загорнуті в 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(), 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

Отже, що ви робите, 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'];
}

Або

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

//Use $results

Примітка : Якщо ви використовуєте метод, як показано нижче ( query()), цей метод повертає 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щоб використовувати кількість повернутих рядків, ви можете отримати 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();

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

Вставте та оновіть зображення PDO

Що ми робимо у 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 і повертається кількість постраждалих рядків.

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

15
Що добре прочитати вище, слід пропагувати згадку: підготовлений вислів забирає будь-яке змістовне використання IN (...) construct.
Євген Рік

24
Питання було "Чому я не повинен використовувати функції mysql_ * в PHP". Ця відповідь, хоча вражаюча і повна корисної інформації, виходить далеко за межі і, як говорить @trejder, - 8 з 10 людей збираються пропустити цю інформацію просто тому, що у них немає 4 годин, щоб витратити на те, щоб пропрацювати це. Це було б набагато ціннішим розбиттям і використовуватиметься як відповіді на кілька, точніших, питань.
Алекс Макміллан

Особисто я віддаю перевагу mysqli та PDO. Але для роботи з матрицею я спробував альтернативу виключення. function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();Вона працює для викидів винятків.
kuldeep.kamboj

ви вказали Doesn't support non-blocking, asynchronous queriesяк причину не використовувати mysql_ - ви також повинні вказати це як причину не використовувати PDO, оскільки PDO також не підтримує це. (але MySQLi це підтримує)
hanshenrik

чи можна використовувати Charset utf8mb4_unicode_ci, оскільки у мене є база даних, яка це використовує?
Райан Стоун

301

Спочатку почнемо зі стандартного коментаря, який ми даємо всім:

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

Давайте переглянемо це, речення за реченням та пояснимо:

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

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

    НОВИЙ! - ext / mysql тепер офіційно застаріла, як з PHP 5.5!

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

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

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

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

  • Бачите Червону скриньку?

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

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

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


6
Є ще одне: я думаю, що ця функція все ще існує в PHP лише з однієї причини - сумісність зі старими, застарілими, але все ще працюючими CMS, електронною комерцією, системами оголошень тощо. Нарешті її буде видалено, і вам доведеться переписати свою заявка ...
Каміль

4
@Kamil: Це правда, але насправді це не є причиною, чому ти не повинен її використовувати. Причина не використовувати його в тому, що це стародавня, невпевнена і т. Д. :)
Привид

4
@Mario - у розробників PHP є процес, і вони щойно проголосували за офіційне знецінення ext / mysql станом на 5.5. Це вже не гіпотетичне питання.
СДК

2
Додавання пари додаткових ліній з перевіреною технікою, такою як PDO або MySQLi, все ще забезпечує простоту використання, яку PHP завжди пропонувався. Я сподіваюся, що заради розробника він / вона знає, що бачення цих богоподібних функцій mysql_ * у будь-якому навчальному посібнику насправді ушкоджує урок, і повинен сказати ОП, що такий код є Soooo 10 років тому - і повинен поставити під сумнів релевантність підручника теж!
FredTheWebGuy

1
На що відповідь слід згадати: підготовлена ​​заява забирає будь-яке змістовне використання IN (...) construct.
Євген Рік

217

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

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

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

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

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

Еквівалентні функції 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()функцію oldschool

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

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

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


8
Будьте обережні з 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 Sure, хоч це і не підтримується багато (0.9.2 було останнім), ви можете створити викопний рахунок , додати до wiki або подати звіт про помилку (без реєстрації IIRC).
mario

pdo_real_escape_string() <- Це навіть реальна функція, я не можу знайти жодну документацію на неї? Будь ласка, опублікуйте джерело для цього.
Райан Стоун

144

Ці mysql_функції:

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

18
№2 однаково стосуєтьсяmysqli_
eggyal

16
якщо бути справедливим, враховуючи зміни в діалекті SQL, навіть PDO не дає вам номер 2 з певною мірою визначеності. Для цього вам знадобиться відповідна обгортка ORM.
SDC

mysql_*функції є оболонками в функцію mysqlnd для нових версій PHP. Тож навіть якщо стара бібліотека клієнтів більше не підтримується, mysqlnd зберігається :)
hakre

Проблема не у багатьох постачальників веб-хостингів може підтримувати такий об’єктно-орієнтований стиль дизайну через застарілу версію php
Raju yourPepe

@RajuGujarati тому знайдіть веб-хостинга, який може. Якщо ваш веб-хостинг не робить, шанси дуже високі, що вони вразливі до атак на їх сервери.
Альнітак

106

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

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

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

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

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

Останнє питання є проблемою.
Але, на мій погляд, запропоноване рішення також не є кращим.
Мені здається, занадто ідеалістичним є мрія, що всі ці користувачі PHP навчиться одразу правильно керувати SQL-запитами. Швидше за все, вони просто механічно змінить mysql_ * на mysqli_ *, залишивши підхід таким же . Тим більше, що mysqli робить підготовлені заяви неймовірно болісними та клопіткими.
Не кажучи вже про те , що місцеві підготовлені заяви мало для захисту від ін'єкцій SQL, і ні 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?

Ну, відповідь була б така:

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

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

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

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

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

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

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

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


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

4
everything is parameterized and safe- це може бути параметризовано, але ваша функція не використовує реально підготовлені оператори.
uınbɐɥs

6
Як Not under active developmentтільки для цього складу «0,01%»? Якщо ви створили щось за допомогою цієї функції автономного режиму, оновіть свою mysql-версію за рік і заведіть непрацюючу систему, я впевнений, що в цій '0,01%' раптом є дуже багато людей. Я б сказав, що deprecatedі not under active developmentтісно пов'язані між собою. Можна сказати, що "немає [гідної] причини" для цього, але факт полягає в тому, що коли пропонують вибір між варіантами, no active developmentце так само погано, як deprecatedя б сказав?
Нанна

1
@MadaraUchiha: Чи можете ви пояснити, як вразливості легко подолати? Особливо у випадках, коли ті самі вразливості не впливають на PDO або MySQLi ... Тому що я не знаю жодної, про яку ви говорите.
ircmaxell

4
@ShaquinTrifonoff: впевнений, він не використовує підготовлені заяви. Але також не існує PDO , який рекомендують більшість людей через MySQLi. Тому я не впевнений, що це має суттєвий вплив. Вищевказаний код (з трохи більше розбору) - це те, що робить PDO, коли готує
заявку

97

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

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

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


3
На жаль, погана підтримка в MySQLi_ * для передачі змінної кількості параметрів (наприклад, коли ви хочете передати список значень для перевірки в пункті IN) заохочує не використовувати параметри, заохочуючи до використання точно таких же об'єднаних запитів, що залиште дзвінки MySQL_ * вразливими.
Кікстарт

5
Але, знову ж таки, невпевненість - це не властива проблема функцій mysql_ *, а проблема неправильного використання.
Агамемнус

2
@Agamemnus Проблема полягає в тому, що mysql_ * полегшує реалізацію цього "неправильного використання", особливо для недосвідчених програмістів. Бібліотеки, що реалізують підготовлені заяви, ускладнюють цей тип помилок.
Тротт

75

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

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

Якби використовувати PDO файлу mysqli, ім'я користувача закінчилося б "enhzflep); drop table users".

Дивіться bobby-tables.com .


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

64

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

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

Тепер у вас є кращі варіанти використання mysqli_ або PDO . Я особисто великий фанат PDO, тому я буду використовувати PDO в решті цієї відповіді. Є профі та противники, але особисто я вважаю, що професіонал набагато переважає кон. Він портативний у декількох двигунах баз даних - будь то ви використовуєте 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, по-друге, це за допомогою підготовленого оператора. Тепер, що заздалегідь задане твердження? Це спосіб повідомити базу даних перед запуском запиту, що це за запит, який ми збираємося виконувати. У цьому випадку ми кажемо базі даних: "Привіт, я запускаю оператор select, що бажає id, userid та передати від користувачів таблиці, де userid є змінною, а pass також є змінною."

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

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

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...

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

Нарешті, це не означає, що це досконалий код. Існує ще багато речей, які можна зробити для її вдосконалення, наприклад, використовуйте хешовані паролі, переконайтесь, що коли ви зберігаєте чуттєву інформацію в базі даних, ви не зберігаєте її в простому тексті, маєте кілька рівнів перевірки - але насправді, якщо Ви просто зміните свій старий код, схильний до ін'єкцій на це, ви будете ДОБРЕ на шляху до написання хорошого коду - і той факт, що Ви до цього часу дійшли і читаєте все ще, дає мені відчути надію, що Ви не будете реалізовувати лише цей тип коду під час написання ваших веб-сайтів та додатків, але щоб ви могли вийти та вивчити ті інші речі, про які я щойно згадував, - і багато іншого. Напишіть найкращий код, який ви можете, не самий основний код, який ледь функціонує.


2
Спасибі за вашу відповідь! У мене є +1! Варто зауважити, що mysql_*сам по собі не є небезпечним, але він просуває незахищений код через погані підручники та відсутність належного API підготовки документа.
Привид

2
неробочені паролі, о жах! = oP Інакше +1 для детального пояснення.
криптовалюта ツ

33

Розширення MySQL є найстарішим із трьох і було оригінальним способом, який розробники використовували для спілкування з MySQL. Це розширення тепер застаріло на користь інших двох альтернатив через вдосконалення нових версій PHP та MySQL.

  • MySQLi - це «покращене» розширення для роботи з базами даних MySQL. Він користується функціями, які доступні в нових версіях сервера MySQL, відкриває розробнику як орієнтований на функції, так і об'єктно-орієнтований інтерфейс, а також багато інших чудових речей.

  • PDO пропонує API, який консолідує більшість функціональних можливостей, які раніше були розповсюджені в основних розширеннях доступу до бази даних, тобто MySQL, PostgreSQL, SQLite, MSSQL тощо. Інтерфейс відкриває об'єкти високого рівня для програміста для роботи з підключеннями до бази даних, запитами та набори результатів, а драйвери низького рівня виконують зв’язок та обробку ресурсів із сервером бази даних. В PDO йде багато дискусій та роботи, і це вважається відповідним методом роботи з базами даних в сучасному професійному кодексі.


21

Я вважаю, що вищезазначені відповіді дійсно тривалі, тому підсумовуючи:

Розширення mysqli має ряд переваг, ключовими розширеннями щодо розширення mysql є:

  • Об'єктно-орієнтований інтерфейс
  • Підтримка підготовлених заяв
  • Підтримка декількох заяв
  • Підтримка транзакцій
  • Розширені можливості налагодження
  • Підтримка вбудованого сервера

Джерело: Огляд MySQLi


Як пояснено у вищенаведених відповідях, альтернативами mysql є mysqli та PDO (PHP Data Objects).

  • API підтримує підготовлені заяви на стороні сервера: Підтримуються MYSQLi та PDO
  • API підтримує готові заяви на стороні клієнта: підтримується лише PDO
  • API підтримує збережені процедури: MySQLi та PDO
  • API підтримує кілька заяв і всю функціональність MySQL 4.1+ - підтримується MySQLi, а також також PDO

І MySQLi, і PDO були представлені в PHP 5.0, тоді як MySQL був представлений до PHP 3.0. Слід зазначити, що MySQL включений у PHP5.x, хоча застарілий у наступних версіях.


2
Ваша відповідь занадто обширна, тоді як справжній підсумок "mysql ext більше не є". Ось і все
Ваше здорове почуття

1
@YourCommonSense Моя відповідь - чому mysqli замінив mysql. Сенс не в тому, щоб сказати, що Mysqli існує сьогодні, тому використовуйте це.
Ані Менон

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

@YourCommonSense Також питання ОП: "Чому я повинен використовувати щось інше, навіть якщо вони працюють на моєму сайті?" і саме тому я вказав на зміни та вдосконалення. Ви можете подивитись на всі інші відповіді, вони довгі, тому я подумав, що варто її підсумувати.
Ані Менон

6

Визначити майже всі mysql_*функції можна за допомогою mysqli або PDO. Просто включіть їх поверх вашої старої програми PHP, і вона працюватиме на PHP7. Моє рішення тут .

<?php

define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null;

function mysql_link($link=null) {
    return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link;
}

function mysql_connect($host, $user, $pass) {
    $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
    return $GLOBALS[MYSQL_LINK];
}

function mysql_pconnect($host, $user, $pass) {
    return mysql_connect($host, $user, $pass);
}

function mysql_select_db($db, $link=null) {
    $link = mysql_link($link);
    return mysqli_select_db($link, $db);
}

function mysql_close($link=null) {
    $link = mysql_link($link);
    return mysqli_close($link);
}

function mysql_error($link=null) {
    $link = mysql_link($link);
    return mysqli_error($link);
}

function mysql_errno($link=null) {
    $link = mysql_link($link);
    return mysqli_errno($link);
}

function mysql_ping($link=null) {
    $link = mysql_link($link);
    return mysqli_ping($link);
}

function mysql_stat($link=null) {
    $link = mysql_link($link);
    return mysqli_stat($link);
}

function mysql_affected_rows($link=null) {
    $link = mysql_link($link);
    return mysqli_affected_rows($link);
}

function mysql_client_encoding($link=null) {
    $link = mysql_link($link);
    return mysqli_character_set_name($link);
}

function mysql_thread_id($link=null) {
    $link = mysql_link($link);
    return mysqli_thread_id($link);
}

function mysql_escape_string($string) {
    return mysql_real_escape_string($string);
}

function mysql_real_escape_string($string, $link=null) {
    $link = mysql_link($link);
    return mysqli_real_escape_string($link, $string);
}

function mysql_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql);
}

function mysql_unbuffered_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql, MYSQLI_USE_RESULT);
}

function mysql_set_charset($charset, $link=null){
    $link = mysql_link($link);
    return mysqli_set_charset($link, $charset);
}

function mysql_get_host_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_host_info($link);
}

function mysql_get_proto_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_proto_info($link);
}
function mysql_get_server_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_server_info($link);
}

function mysql_info($link=null) {
    $link = mysql_link($link);
    return mysqli_info($link);
}

function mysql_get_client_info() {
    $link = mysql_link();
    return mysqli_get_client_info($link);
}

function mysql_create_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "CREATE DATABASE `$db`");
}

function mysql_drop_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "DROP DATABASE `$db`");
}

function mysql_list_dbs($link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, "SHOW DATABASES");
}

function mysql_list_fields($db, $table, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    $table = str_replace('`', '', mysqli_real_escape_string($link, $table));
    return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`");
}

function mysql_list_tables($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "SHOW TABLES FROM `$db`");
}

function mysql_db_query($db, $sql, $link=null) {
    $link = mysql_link($link);
    mysqli_select_db($link, $db);
    return mysqli_query($link, $sql);
}

function mysql_fetch_row($qlink) {
    return mysqli_fetch_row($qlink);
}

function mysql_fetch_assoc($qlink) {
    return mysqli_fetch_assoc($qlink);
}

function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) {
    return mysqli_fetch_array($qlink, $result);
}

function mysql_fetch_lengths($qlink) {
    return mysqli_fetch_lengths($qlink);
}

function mysql_insert_id($qlink) {
    return mysqli_insert_id($qlink);
}

function mysql_num_rows($qlink) {
    return mysqli_num_rows($qlink);
}

function mysql_num_fields($qlink) {
    return mysqli_num_fields($qlink);
}

function mysql_data_seek($qlink, $row) {
    return mysqli_data_seek($qlink, $row);
}

function mysql_field_seek($qlink, $offset) {
    return mysqli_field_seek($qlink, $offset);
}

function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
    return ($params === null)
        ? mysqli_fetch_object($qlink, $class)
        : mysqli_fetch_object($qlink, $class, $params);
}

function mysql_db_name($qlink, $row, $field='Database') {
    mysqli_data_seek($qlink, $row);
    $db = mysqli_fetch_assoc($qlink);
    return $db[$field];
}

function mysql_fetch_field($qlink, $offset=null) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    return mysqli_fetch_field($qlink);
}

function mysql_result($qlink, $offset, $field=0) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    $row = mysqli_fetch_array($qlink);
    return (!is_array($row) || !isset($row[$field]))
        ? false
        : $row[$field];
}

function mysql_field_len($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->length : false;
}

function mysql_field_name($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgname) ? $field->name : $field->orgname;
}

function mysql_field_table($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgtable) ? $field->table : $field->orgtable;
}

function mysql_field_type($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->type : false;
}

function mysql_free_result($qlink) {
    try {
        mysqli_free_result($qlink);
    } catch (Exception $e) {
        return false;
    }
    return true;
}

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

1

Функції, подібні до цієї mysql_connect(), mysql_query()є типом попередньої версії PHP, тобто (PHP 4), і тепер не використовуються.

Вони замінюються mysqli_connect(), так mysqli_query()само в останньому PHP5.

Це причина помилки.


2
PHP 5 не був останнім більше 2 років.
Привид

1

Знищено MySQL у PHP 5.5.0 та видалено в PHP 7.0.0. Для великої та старої програми це важко шукати та замінювати кожну функцію.

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


-9

Функції mysql_ * були зняті (станом на PHP 5.5 ) з огляду на те, що були розроблені кращі функції та структури коду. Той факт, що функцію було знято, означає, що більше зусиль для її покращення в плані продуктивності та безпеки не буде докладено, а це означає , що вона є меншою доказовою .

Якщо вам потрібно більше причин:

  • функції mysql_ * не підтримують підготовлені оператори.
  • функції mysql_ * не підтримують прив'язку параметрів.
  • функції mysql_ * не мають функціональності для об'єктно-орієнтованого програмування.
  • список продовжується ...

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