Pytanie Dlaczego nie powinienem używać funkcji mysql_ * w PHP?


Jakie są techniczne powody, dla których nie należy korzystać mysql_* Funkcje? (na przykład. mysql_query(), mysql_connect() lub mysql_real_escape_string())?

Dlaczego powinienem używać czegoś innego, nawet jeśli działają na mojej stronie?

Jeśli nie działają one w mojej witrynie, dlaczego pojawiają się błędy, takie jak

Ostrzeżenie: mysql_connect (): Brak takiego pliku lub katalogu


2196
2017-10-12 13:18


pochodzenie


Błąd: Fatal error: Uncaught Error: wywołanie niezdefiniowanej funkcji mysql_connect () ... - Bimal Poudel
Przestarzały sam jest wystarczającym powodem, aby ich uniknąć - Sasa1234


Odpowiedzi:


Rozszerzenie MySQL:

  • Nie jest aktywnie rozwijany
  • Jest oficjalnie przestarzałe od PHP 5.5 (wydany w czerwcu 2013 r.).
  • Był oddalony całkowicie od wersji 7.0 (wydany w grudniu 2015)
    • Oznacza to, że od 31 grudnia 2018 r nie będzie istnieć w żadnej obsługiwanej wersji PHP. Obecnie tylko dostaje bezpieczeństwo aktualizacje.
  • Brakuje interfejsu OO
  • Nie obsługuje:
    • Nie blokujące, asynchroniczne zapytania
    • Przygotowane oświadczenia lub sparametryzowane zapytania
    • Przechowywane procedury
    • Wiele stwierdzeń
    • Transakcje
    • "Nowa" metoda uwierzytelniania hasłem (domyślnie włączona w MySQL 5.6, wymagana w 5.7)
    • Cała funkcjonalność w MySQL 5.1

Ponieważ jest przestarzałe, użycie go sprawia, że ​​Twój kod nie jest w przyszłości dowodem.

Brak wsparcia dla przygotowanych stwierdzeń jest szczególnie ważny, ponieważ zapewniają one wyraźniejszą, mniej podatną na błędy metodę ucieczki i cytowania danych zewnętrznych, niż ręczne jej unikanie za pomocą oddzielnego wywołania funkcji.

Widzieć porównanie rozszerzeń SQL.


1814
2018-01-01 11:52



Przestarzały sam jest wystarczającym powodem, aby ich uniknąć. Nie będzie ich tam jeden dzień, a nie będziesz szczęśliwy, jeśli będziesz na nich polegać. Reszta to tylko lista rzeczy, które za pomocą starych rozszerzeń powstrzymały ludzi przed nauką. - Tim Post♦
Wycofanie nie jest magiczną kulą, o której wszyscy myślą. Sam PHP nie pojawi się pewnego dnia, a my polegamy na narzędziach, którymi dysponujemy do tej pory. Kiedy będziemy musieli zmienić narzędzia, będziemy. - Lightness Races in Orbit
@LightnessRacesinOrbit - Wycofanie nie jest magiczną kulą, jest to flaga, która mówi: "Rozumiemy, że to jest do bani, więc nie zamierzamy go dłużej wspierać". O ile lepsza ochrona kodu w przyszłości jest dobrym powodem, aby odejść od przestarzałych funkcji, to nie jest jedyna (lub nawet główna). Zmień narzędzia, ponieważ istnieją lepsze narzędzia, a nie dlatego, że jesteś do tego zmuszony. (I zmiana narzędzi, zanim zostaniesz zmuszona oznacza, że ​​nie uczysz się nowych tylko dlatego, że twój kod przestał działać i wymaga naprawy wczoraj ... to jest najgorszy czas, aby nauczyć się nowych narzędzi). - Quentin
Jedną z rzeczy, o których nie wspomniałem wcześniej o braku przygotowanych stwierdzeń, jest problem z wydajnością. Za każdym razem, gdy wydasz oświadczenie, coś musi go skompilować, aby demon MySQL mógł go zrozumieć. Z tym API, jeśli wydasz 200 000 tego samego zapytania w pętli, to 200 000 razy zapytanie musi zostać skompilowane dla MySQL, aby to zrozumieć. Dzięki przygotowanym instrukcjom jest ono kompilowane jeden raz, a następnie sparametryzowane wartości w skompilowanym SQL. - Goldentoa11
@symcbean, Z pewnością tak nie obsługa przygotowanych oświadczeń. To w rzeczywistości główny powód, dla którego jest przestarzały. Bez (łatwych w użyciu) przygotowanych instrukcji rozszerzenie mysql często pada ofiarą ataków typu SQL injection. - rustyx


PHP oferuje trzy różne interfejsy API do łączenia się z MySQL. To są mysql(usunięte od PHP 7), mysqli, i PDO rozszerzenia.

The mysql_* funkcje były bardzo popularne, ale ich użycie nie jest już zalecane. Zespół zajmujący się dokumentacją dyskutuje na temat bezpieczeństwa bazy danych, a wykształcenie użytkowników, aby odeszli od powszechnie używanego rozszerzenia ext / mysql, jest częścią tego (sprawdź php.internals: wycofanie ext / mysql).

A późniejszy zespół programistów PHP podjął decyzję o wygenerowaniu E_DEPRECATED błędy, gdy użytkownicy łączą się z MySQL, czy przez mysql_connect(), mysql_pconnect() lub wbudowana wbudowana funkcja niejawnego połączenia ext/mysql.

ext/mysql był oficjalnie przestarzałe od PHP 5.5 i był usunięte z PHP 7.

Zobacz Red Box?

Kiedy przejdziesz dalej mysql_* strona podręcznika funkcji, zobaczysz czerwone pole, wyjaśniające, że nie powinno już być używane.

Czemu


Oddalam się od ext/mysql to nie tylko bezpieczeństwo, ale także dostęp do wszystkich funkcji bazy danych MySQL.

ext/mysql został zbudowany dla MySQL 3.23 i od tego czasu ma tylko kilka dodatków, ale w większości zachowuje zgodność z tą starą wersją, co sprawia, że ​​kod jest trudniejszy do utrzymania. Brakujące funkcje, które nie są obsługiwane przez ext/mysql zawierać: (z podręcznika PHP).

Powód do nieużywania mysql_* funkcjonować:

  • Nie pod aktywnym rozwojem
  • Usunięto z PHP 7
  • Brakuje interfejsu OO
  • Nie obsługuje niekblokujących, asynchronicznych zapytań
  • Nie obsługuje przygotowanych instrukcji lub sparametryzowane zapytania
  • Nie obsługuje procedur przechowywanych
  • Nie obsługuje wielu instrukcji
  • Nie obsługuje transakcje
  • Nie obsługuje wszystkich funkcji MySQL 5.1

Powyższy punkt cytowany z odpowiedzi Quentina

Brak obsługi przygotowanych stwierdzeń jest szczególnie ważny, ponieważ zapewniają one wyraźniejszą, mniej podatną na błędy metodę ucieczki i cytowania danych zewnętrznych, niż ręczne jej unikanie za pomocą oddzielnego wywołania funkcji.

Zobacz porównanie rozszerzeń SQL.


Pomijanie ostrzeżeń o wycofaniu

Podczas konwersji kodu na MySQLi/PDO, E_DEPRECATED błędy można wyłączyć poprzez ustawienie error_reporting w php.ini wykluczyć E_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Zauważ, że to również się ukryje inne ostrzeżenia o wycofaniu, które jednak mogą dotyczyć rzeczy innych niż MySQL. (z podręcznika PHP)

Artykuł PDO vs. MySQLi: Którego należy użyć? przez Dejan Marjanovic pomoże ci wybrać.

I jest lepszy sposób PDO, a teraz piszę proste PDO seminarium.


Prosty i krótki samouczek PDO


P. Pierwsze pytanie w mojej głowie brzmiało: czym jest "PDO"?

A. "PDO - PHP Data Objects - jest warstwą dostępu do bazy danych zapewniającą jednolity sposób dostępu do wielu baz danych. "

alt text


Łączenie z MySQL

Z mysql_* funkcja lub możemy powiedzieć to w starym stylu (przestarzałe w PHP 5.5 i wyżej)

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

Z PDO: Wszystko, co musisz zrobić, to stworzyć nowe PDO obiekt. Konstruktor akceptuje parametry określające źródło bazy danych PDOKonstruktor przeważnie przyjmuje cztery parametry, które są DSN (nazwa źródła danych) i opcjonalnie username, password.

Tutaj myślę, że jesteś obeznany z wszystkimi wyjątkiem DSN; to jest nowe w PDO. ZA DSN jest po prostu ciągiem opcji, które mówią PDO który sterownik i szczegóły połączenia. Aby uzyskać dalsze informacje, sprawdź PDO MySQL DSN.

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

Uwaga: możesz także użyć charset=UTF-8, ale czasami powoduje błąd, więc lepiej go użyć utf8.

Jeśli wystąpi jakikolwiek błąd połączenia, to wyrzuci a PDOException obiekt, który może zostać przechwycony do obsługi Exception dalej.

Dobra lektura: Połączenia i zarządzanie połączeniami ¶ 

Można również przekazać kilka opcji sterownika jako tablicę do czwartego parametru. Polecam przekazanie parametru, który stawia PDO w trybie wyjątku. Ponieważ niektóre PDO sterowniki nie obsługują natywnych przygotowanych instrukcji, więc PDO wykonuje emulację przygotowania. Umożliwia również ręczne włączenie tej emulacji. Aby użyć instrukcji natywnych przygotowanych po stronie serwera, należy jawnie ustawić to false.

Drugim jest wyłączenie przygotowanej emulacji, która jest włączona w MySQLSterownik domyślnie, ale przygotuj emulację powinna być wyłączona do użycia PDO bezpiecznie.

Później wyjaśnię, dlaczego należy wyłączyć emulację przygotowawczą. Aby znaleźć przyczynę, sprawdź ten post.

Jest użyteczny tylko wtedy, gdy używasz starej wersji MySQL których nie polecam.

Poniżej znajduje się przykład tego, jak możesz to zrobić:

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

Czy możemy ustawić atrybuty po zbudowaniu PDO?

tak, możemy także ustawić pewne atrybuty po konstrukcji PDO z setAttribute metoda:

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

Obsługa błędów


Obsługa błędów jest znacznie łatwiejsza PDO niż mysql_*.

Powszechna praktyka podczas używania mysql_* jest:

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

OR die() nie jest dobrym sposobem na naprawienie błędu, ponieważ nie możemy go obsłużyć die. Spowoduje to natychmiastowe zakończenie skryptu, a następnie wyświetlenie błędu na ekranie, który zazwyczaj NIE chcesz wyświetlać użytkownikom końcowym, i niech przeklęci hakerzy odkrywają Twój schemat. Alternatywnie, zwracane są wartości mysql_* funkcje często mogą być używane w połączeniu z mysql_error () do obsługi błędów.

PDO oferuje lepsze rozwiązanie: wyjątki. Cokolwiek robimy PDO powinien być owinięty w try-catch blok. Możemy wymusić PDO do jednego z trzech trybów błędów, ustawiając atrybut trybu błędu. Trzy tryby obsługi błędów znajdują się poniżej.

  • PDO::ERRMODE_SILENT. Po prostu ustawia kody błędów i działa tak samo jak mysql_* gdzie musisz sprawdzić każdy wynik, a następnie spojrzeć na $db->errorInfo(); aby uzyskać szczegóły błędu.
  • PDO::ERRMODE_WARNING Podnieść E_WARNING. (Ostrzeżenia czasu wykonania (błędy niekrytyczne) Wykonanie skryptu nie jest zatrzymywane.)
  • PDO::ERRMODE_EXCEPTION: Zgłaszaj wyjątki. Przedstawia błąd podniesiony przez ChNP. Nie powinieneś rzucać PDOException z własnego kodu. Widzieć Wyjątki aby uzyskać więcej informacji na temat wyjątków w PHP. Działa bardzo podobnie or die(mysql_error());, kiedy nie zostanie złapany. Ale w przeciwieństwie do or die(), PDOException może zostać złapany i załatwiony z wdziękiem, jeśli zdecydujesz się to zrobić.

Dobra lektura:

Lubić:

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

I możesz go zawinąć try-catch, jak poniżej:

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());
}

Nie musisz sobie z tym radzić try-catch teraz. Możesz go złapać w dowolnym momencie, ale zdecydowanie polecam try-catch. Również może być sensownym złapaniem go poza funkcją, która wywołuje PDO rzeczy:

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

Ponadto możesz obsłużyć or die() lub możemy powiedzieć jak mysql_*, ale będzie naprawdę zróżnicowana. Możesz ukryć niebezpieczne komunikaty o błędach w produkcji obracając display_errors off i po prostu czytając swój dziennik błędów.

Teraz, po przeczytaniu wszystkich powyższych rzeczy, prawdopodobnie myślisz: co to do cholery jest, gdy po prostu chcę zacząć pochylać się prosto SELECT, INSERT, UPDATE, lub DELETE sprawozdania? Nie martw się, zaczynamy:


Wybieranie danych

PDO select image

Więc co robisz w mysql_* jest:

<?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'];
}

Teraz w PDOmożesz to zrobić tak:

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

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

Lub

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

//Use $results

Uwaga: Jeśli używasz metody jak poniżej (query()), ta metoda zwraca a PDOStatement obiekt. Więc jeśli chcesz pobrać wynik, użyj go jak powyżej.

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

W PDO Data jest uzyskiwany przez ->fetch(), metoda obsługi wyciągu. Przed wezwaniem do pobrania najlepszym sposobem byłoby poinformowanie PDO, w jaki sposób chcesz pobrać dane. W sekcji poniżej wyjaśniam to.

Fetch Modes

Zwróć uwagę na użycie PDO::FETCH_ASSOC w fetch() i fetchAll() kod powyżej. To mówi PDO zwrócić wiersze jako tablicę asocjacyjną z nazwami pól jako kluczami. Istnieje również wiele innych trybów pobierania, które będę wyjaśniał jeden po drugim.

Przede wszystkim wyjaśniam, jak wybrać tryb pobierania:

 $stmt->fetch(PDO::FETCH_ASSOC)

W powyższym używam fetch(). Możesz także użyć:

Teraz przechodzę do trybu pobierania:

  • PDO::FETCH_ASSOC: zwraca tablicę indeksowaną według nazwy kolumny zwróconej w zestawie wyników
  • PDO::FETCH_BOTH (domyślnie): zwraca tablicę indeksowaną zarówno przez nazwę kolumny, jak i 0-indeksowany numer kolumny, zwracaną w zestawie wyników

Jest jeszcze więcej możliwości wyboru! Przeczytaj o nich wszystko w PDOStatement Pobierz dokumentację..

Uzyskiwanie liczby wierszy:

Zamiast używać mysql_num_rows aby uzyskać liczbę zwróconych wierszy, można uzyskać PDOStatement i robić rowCount(), lubić:

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

Pobieranie ostatniego wstawionego identyfikatora

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

Wstawianie i aktualizowanie lub usuwanie instrukcji

Insert and update PDO image

Co robimy w mysql_* funkcja to:

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

I w pdo, to samo można zrobić przez:

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

W powyższym zapytaniu PDO::exec wykonaj instrukcję SQL i zwróć liczbę dotkniętych wierszy.

Wstawianie i usuwanie będą omówione później.

Powyższa metoda jest użyteczna tylko wtedy, gdy nie używasz zmiennej w zapytaniu. Ale kiedy potrzebujesz użyć zmiennej w zapytaniu, nigdy nie próbuj jak powyżej i tam przygotowane oświadczenie lub sparametryzowane oświadczenie jest.


Przygotowane oświadczenia

Q. Co to jest przygotowane oświadczenie i dlaczego ich potrzebuję?
ZA. Przygotowana instrukcja jest wstępnie skompilowaną instrukcją SQL, która może być wykonywana wiele razy, wysyłając tylko dane do serwera.

Typowy przepływ pracy przy użyciu przygotowanego wyciągu jest następujący (cytowane z Wikipedii trzy 3 punkty):

  1. Przygotować: Szablon oświadczenia jest tworzony przez aplikację i wysyłany do systemu zarządzania bazami danych (DBMS). Niektóre wartości pozostają nieokreślone, zwane parametrami, zmiennymi zastępczymi lub zmiennymi wiązania (oznaczone jako ? poniżej):

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

  2. System DBMS analizuje, kompiluje i wykonuje optymalizację zapytań w szablonie wyciągu i przechowuje wynik bez jego wykonywania.

  3. Wykonać: W późniejszym czasie aplikacja dostarcza (lub wiąże) wartości parametrów, a DBMS wykonuje instrukcję (prawdopodobnie zwracając wynik). Aplikacja może wykonać instrukcję tyle razy, ile chce, z różnymi wartościami. W tym przykładzie może podać "Chleb" dla pierwszego parametru i 1.00dla drugiego parametru.

Możesz użyć przygotowanej instrukcji, dołączając symbole zastępcze do kodu SQL. Zasadniczo są to trzy bez elementów zastępczych (nie próbuj tego ze zmienną powyżej), jeden z nienazwanymi symbolami zastępczymi i jeden z nazwanymi symbolami zastępczymi.

Q. A teraz, co nazywają się symbolami zastępczymi i jak z nich korzystać?
ZA. Nazwani placeholders. Używaj opisowych nazw poprzedzonych dwukropkiem zamiast znaków zapytania. Nie dbamy o pozycję / kolejność wartości dla posiadacza nazwy miejsca:

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

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

Możesz także powiązać za pomocą tablicy 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);

Kolejna fajna funkcja dla OOP znajomi nazywają placeholderami możliwość wstawiania obiektów bezpośrednio do bazy danych, zakładając, że właściwości pasują do nazwanych pól. Na przykład:

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. A więc, czym są bezimienne obiekty zastępcze i jak ich używać?
ZA. Miejmy przykład:

<?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();

i

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

Powyższe widać ? zamiast nazwy takiej jak nazwisko właściciela nazwy. Teraz w pierwszym przykładzie przypisujemy zmienne do różnych symboli zastępczych ($stmt->bindValue(1, $name, PDO::PARAM_STR);). Następnie przypisujemy wartości tym elementom zastępczym i wykonujemy instrukcję. W drugim przykładzie pierwszy element tablicy przechodzi do pierwszego ? a drugi do drugiego ?.

UWAGA: W nienazwane placeholders musimy zadbać o właściwą kolejność elementów w tablicy, do której przechodzimy PDOStatement::execute() metoda.


SELECT, INSERT, UPDATE, DELETE przygotowane zapytania

  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();
    

UWAGA:

jednak PDO i / lub MySQLi nie są całkowicie bezpieczne. Sprawdź odpowiedź Czy PDO przygotowało oświadczenia wystarczające, aby zapobiec iniekcjom SQL? przez ircmaxell. Ponadto, cytuję część z jego odpowiedzi:

$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



Co dobrze przeczytać powyżej, należy wspomnieć: przygotowane oświadczenie zabrać wszelkie sensowne wykorzystanie IN (...) construct. - Eugen Rieck
@Amine, Nie, nie jest! :] Podczas NullPoiиteя Naprawdę świetnie się spisała, to na pewno nie jest dobra lektura, bo to jest sposób na długie. Jestem prawie pewien, że 8 na 10 odwiedzających po prostu go opuści. I masz również wyjaśnienie, dlaczego ta odpowiedź nie jest głosowana w pierwszej kolejności. ZA tl;dr część na początku byłaby dobrym pomysłem, jak sądzę. - trejder
Pytanie brzmiało "Dlaczego nie powinienem używać funkcji mysql_ * w PHP". Ta odpowiedź, mimo że jest imponująca i pełna przydatnych informacji, jest WYJAŚNANA poza zakresem i jak mówi @trejder - 8 na 10 osób straci tę informację po prostu dlatego, że nie ma 4 godzin, aby spędzić czas na przepracowaniu to. Byłoby to o wiele bardziej wartościowe rozbite i wykorzystane jako odpowiedź na kilka, bardziej precyzyjnych pytań. - Alex McMillan
Persoanlly Preferuję mysqli i PDO. Ale do obsługi matryc próbowałem alternatywy wyjątku function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx(); Działa na rzucanie wyjątków. - kuldeep.kamboj


Najpierw zacznijmy od standardowego komentarza, który dajemy wszystkim:

Proszę, nie używaj mysql_* funkcje w nowym kodzie. Nie są już utrzymywane i są oficjalnie przestarzałe. Zobacz czerwone pudełko? Uczyć się o przygotowane oświadczenia zamiast tego i użyj CHNP lub MySQLi - Ten artykuł pomoże ci zdecydować, które. Jeśli wybierzesz CHNP, oto dobry samouczek.

Przyjrzyjmy się temu, zdanie po zdaniu i wyjaśnij:

  • Nie są już utrzymywane i są oficjalnie uznawane za przestarzałe

    Oznacza to, że społeczność PHP stopniowo rezygnuje z obsługi tych bardzo starych funkcji. Prawdopodobnie nie istnieją w przyszłej (najnowszej) wersji PHP! Dalsze korzystanie z tych funkcji może spowodować złamanie kodu w (nie) dalekiej przyszłości.

    NOWY! - ext / mysql jest teraz oficjalnie przestarzałe od PHP 5.5!

    Nowsza! ext / mysql został usunięty w PHP 7.

  • Zamiast tego powinieneś nauczyć się gotowych wypowiedzi

    mysql_* rozszerzenie nie obsługuje przygotowane oświadczenia, która jest (między innymi) bardzo skutecznym środkiem zaradczym przeciwko Injection SQL. Naprawiono bardzo poważną lukę w aplikacjach zależnych od MySQL, która umożliwia atakującym uzyskanie dostępu do skryptu i wykonanie każde możliwe zapytanie w twojej bazie danych.

    Aby uzyskać więcej informacji, zobacz Jak mogę zapobiec iniekcji SQL w PHP?

  • Zobacz Red Box?

    Kiedy idziesz do dowolnego mysql strona podręcznika funkcji, zobaczysz czerwone pole, wyjaśniające, że nie powinno już być używane.

  • Użyj albo PDO lub MySQLi

    Są lepsze, bardziej solidne i dobrze zbudowane alternatywy, PDO - obiekt bazy danych PHP, który oferuje pełne podejście do interakcji z bazami danych, oraz MySQLi, który jest poprawą specyficzną dla MySQL.


280
2017-12-24 23:30



Jest jeszcze jedno: myślę, że ta funkcja nadal istnieje w PHP tylko z jednego powodu - kompatybilności ze starym, przestarzałym, ale wciąż działającym systemem CMS, e-commerce, systemami tablic ogłoszeniowych itp. W końcu zostanie usunięty i będziesz musiał przepisać swoje podanie... - Kamil
@Kamil: To prawda, ale nie jest to powód, dla którego nie powinieneś go używać. Powodem, by go nie używać, jest to, że jest prastary, niepewny itp. :) - Madara Uchiha♦
@Mario - twórcy PHP mają proces i właśnie głosowali za formalnym wycofaniem ext / mysql od wersji 5.5. To już nie jest hipotetyczny problem. - SDC
Dodanie kilku dodatkowych linii za pomocą sprawdzonej techniki, takiej jak PDO lub MySQLi, zapewnia łatwość użycia, którą PHP zawsze zapewniał. Mam nadzieję, że dla dewelopera on / ona wie, że zobaczenie tych okropnych funkcji mysql_ * w jakimkolwiek tutorialu faktycznie odciąża lekcję i powinien powiedzieć OP, że ten rodzaj kodu jest taki 10 lat temu- i powinien zadawać pytania trafność tutorialu też! - FredTheWebGuy
O czym powinna wspomnieć odpowiedź: przygotowane oświadczenie zabrać wszelkie sensowne wykorzystanie IN (...) construct. - Eugen Rieck


Łatwość użycia

Przyczyny analityczne i syntetyczne zostały już wspomniane. Dla nowoprzybyłych istnieje ważniejsza zachęta do zaprzestania używania funkcji mysql_.

Współczesne interfejsy API baz danych są po prostu łatwiej używać.

To głównie powiązane parametry co może uprościć kod. I z doskonałe tutoriale (jak widać powyżej) przejście do CHNP nie jest nadmiernie uciążliwa.

Przepisanie większej bazy kodu naraz wymaga jednak czasu. Raison d'être dla tej pośredniej alternatywy:

Równoważne funkcje pdo_ * w miejsce mysql_ *

Za pomocą <pdo_mysql.php> możesz przełączyć ze starej funkcji mysql na minimalny wysiłek. Dodaje pdo_ owijki funkcyjne, które zastępują ich mysql_ odpowiedniki.

  1. Po prostu include_once("pdo_mysql.php"); w każdym skrypcie wywołania, który musi wchodzić w interakcje z bazą danych.

  2. Usunąć mysql_ prefiks funkcji wszędzie i zastąp go pdo_.

    • mysql_connect() staje się pdo_connect()
    • mysql_query() staje się pdo_query()
    • mysql_num_rows() staje się pdo_num_rows()
    • mysql_insert_id() staje się pdo_insert_id()
    • mysql_fetch_array() staje się pdo_fetch_array()
    • mysql_fetch_assoc() staje się pdo_fetch_assoc()
    • mysql_real_escape_string() staje się pdo_real_escape_string()
    • i tak dalej... 

       
  3. Twój kod będzie działał podobnie i nadal będzie wyglądać tak samo:

    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]";
    }
    

Gotowe.
Twój kod jest za pomocą CHNP.
Teraz czas na to wykorzystać to.

Parametry powiązane mogą być łatwe w użyciu

Potrzebujesz tylko mniej nieporęcznego API.

pdo_query() dodaje bardzo łatwe wsparcie dla parametrów związanych. Konwersja starego kodu jest prosta:

Przenieś swoje zmienne z ciągu SQL.

  • Dodaj je jako parametry funkcji rozdzielane przecinkami do pdo_query().
  • Umieść znaki zapytania ? jako placeholder, gdzie zmienne były wcześniej.
  • Pozbyć się ' pojedyncze cytaty, które poprzednio zawierały wartości / zmienne łańcucha.

Przewaga staje się bardziej oczywista w przypadku dłuższego kodu.

Często zmienne łańcuchowe nie są interpolowane tylko na SQL, ale łączone są z wywoływaniem między nimi.

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")

Z ? stosowane placeholder nie musisz się tym przejmować:

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

Pamiętaj, że pdo_ * nadal pozwala albo lub.
Po prostu nie uciekaj od zmiennej i powiązać go w tym samym zapytaniu.

  • Element zastępczy jest zapewniany przez rzeczywisty PDO za nim.
  • Tak więc dozwolone :named listy zastępcze później.

Co ważniejsze, możesz bezpiecznie przekazać $ _REQUEST [] zmienne za każdym zapytaniem. Po przesłaniu <form> pola pasują do struktury bazy danych, ale są jeszcze krótsze:

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

Tak wiele prostoty. Ale wróćmy do kilku innych porad dotyczących przepisywania i powodów technicznych, dlaczego warto się ich pozbyć mysql_i ucieczka.

Napraw lub usuń starą szkołę sanitize() funkcjonować

Po przeliczeniu wszystkich mysql_ wzywa do pdo_query ze związanymi paramami usuń wszystkie zbędne pdo_real_escape_string połączenia.

W szczególności powinieneś naprawić każdy sanitize lub clean lub filterThis lub clean_data funkcje reklamowane przez datowane samouczki w jednej lub w drugiej formie:

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

Największy błąd to brak dokumentacji. Co ważniejsze kolejność filtrowania była w niewłaściwej kolejności.

  • Prawidłowa kolejność byłaby: zaniechana stripslashes jako najgłębsze wezwanie trim, potem strip_tags, htmlentities dla kontekstu wyjściowego i tylko na końcu _escape_string ponieważ jego aplikacja powinna bezpośrednio poprzedzać interpretercję SQL.

  • Ale jako pierwszy krok pozbyć się czegoś _real_escape_string połączenie.

  • Być może będziesz musiał zachować resztę swoich sanitize() funkcja na teraz, jeśli baza danych i przepływ aplikacji oczekują łańcuchów bezpiecznych pod względem kontekstu HTML. Dodaj komentarz, że od tej pory dotyczy tylko ucieczki HTML.

  • Obsługa łańcuchów / wartości jest delegowana do PDO i jej sparametryzowanych instrukcji.

  • Jeśli była jakaś wzmianka stripslashes() w swojej funkcji odkażania może wskazywać na wyższy poziom nadzoru.

    Historyczna notatka na magic_quotes. Ta funkcja jest słusznie przestarzała. Często jest niepoprawnie przedstawiana jako nieudana bezpieczeństwo funkcja jednak. Ale magic_quotes są tak samo nieudaną funkcją bezpieczeństwa, jak piłki tenisowe zawiodły jako źródło żywienia. To po prostu nie było ich celem.

    Oryginalna implementacja w PHP2 / FI wprowadziła ją wprost "Cytaty będą automatycznie usuwane, co ułatwi przekazywanie danych formularzy bezpośrednio do zapytań msql"W szczególności był to przypadkowo bezpieczny w użyciu mSQL, ponieważ obsługuje tylko ASCII.
      Następnie PHP3 / Zend ponownie narzucił magic_quotes na MySQL i źle go udokumentował. Ale pierwotnie było po prostu wygoda, nie zamierzam mieć bezpieczeństwa.

Jak przygotowane instrukcje różnią się

Podczas mieszania zmiennych łańcuchowych w kwerendach SQL, nie tylko staje się on bardziej skomplikowany do naśladowania. Jest to również niepotrzebny wysiłek, aby MySQL ponownie segregował kod i dane.

Zastrzyki SQL są po prostu wtedy dane spływają do kodu kontekst. Serwer bazy danych nie może później wykryć, gdzie PHP początkowo sklejało zmienne pomiędzy klauzulami zapytania.

Z powiązanymi parametrami oddzielasz kod SQL i wartości kontekstu SQL w swoim kodzie PHP. Ale za kulisami ponownie się nie tasuje (z wyjątkiem PDO :: EMULATE_PREPARES). Twoja baza danych otrzymuje niezalene polecenia SQL i wartości zmiennych 1: 1.

Podczas gdy ta odpowiedź podkreśla, że ​​powinieneś zadbać o czytelność zalet upuszczania mysql_. Niekiedy występuje także przewaga wydajności (powtarzane WSTAWy o różnych wartościach) ze względu na widoczne i techniczne rozdzielenie danych / kodów.

Uważaj, że wiązanie parametrów nadal nie jest magicznym rozwiązaniem typu "one-stop" wszystko Wstrzyknięcia SQL. Obsługuje najczęściej używane dane / wartości. Nie można jednak dodawać do białej listy nazw kolumn / identyfikatorów tabel, pomocy przy tworzeniu klauzul dynamicznych ani zwykłych list wartości.

Zastosowanie hybrydowego PDO

Te pdo_* funkcje opakowania tworzą przyjazny dla kodu interfejs API z przerwami. (To prawie co MYSQLI mogło być, gdyby nie było to przesunięcie sygnatury funkcji idiosynkratycznej). Wykazywali też prawdziwy CHNP w większości przypadków.
Przepisywanie nie musi przestać używać nowych nazw funkcji pdo_. Możesz przejść jeden krok po kroku pdo_query () do zwykłego wywołania $ pdo-> prepare () -> execute ().

Jednak najlepiej zacząć od uproszczenia. Na przykład typowe pobieranie wyników:

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

Można go zastąpić tylko iteracją foreach:

foreach ($result as $row) {

Albo jeszcze lepiej: bezpośrednie i pełne pobieranie tablic:

$result->fetchAll();

Otrzymasz bardziej pomocne ostrzeżenia w większości przypadków niż PDO lub mysql_ zwykle dostarczają po nieudanych zapytaniach.

Inne opcje

Tak więc mam nadzieję, że wizualizowane niektóre praktyczny powody i warty zachodu szlak do zrzucenia mysql_.

Po prostu przełączam się na  nie całkiem to wycina. pdo_query() jest także tylko frontendem na to.

Dopóki nie wprowadzisz również wiązania parametrów lub nie będziesz mógł wykorzystać czegoś innego z ładniejszego interfejsu API, jest to bezcelowe przełączanie. Mam nadzieję, że jest to przedstawione na tyle proste, aby nie zniechęcać przybyszów. (Edukacja zazwyczaj działa lepiej niż zakaz).

Chociaż kwalifikuje się do kategorii Najprostsza rzecz, która może dotyczyć pracy, nadal jest to kod eksperymentalny. Napisałem to w weekend. Istnieje jednak mnóstwo alternatyw. Po prostu google dla Odtworzenie bazy danych PHP i przeglądaj trochę. Zawsze istniało i będzie wiele doskonałych bibliotek do takich zadań.

Jeśli chcesz dalej uprościć interakcję z bazą danych, twórcy map, tacy jak Paryż / Idiorm warto spróbować. Tak jak nikt nie używa już pozbawionego smaku DOM w JavaScript, nie musisz już teraz opiekować się surowym interfejsem bazy danych.


201
2017-10-12 13:22



Uważaj na pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST); funkcja - tj: pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true'); - rickyduck
@Tom Pewnie, choć nie jest to zbyt wiele (0.9.2 było ostatnim), możesz stworzyć rachunek kopalny, dodaj do wiki lub pliku a Zgłoszenie błędu (bez rejestracji IIRC). - mario


The mysql_ Funkcje:

  1. są przestarzałe - nie są już konserwowane
  2. nie pozwalają na łatwe przejście do innego zaplecza bazy danych
  3. nie obsługują przygotowanych oświadczeń, stąd
  4. zachęcać programistów do stosowania konkatenacji do budowania zapytań, co prowadzi do luk SQL injection

136
2018-01-01 17:42



# 2 jest równie prawdziwe mysqli_ - eggyal
aby być uczciwym, biorąc pod uwagę różnice w dialekcie SQL, nawet PDO nie daje ci # 2 z jakimkolwiek stopniem pewności. Do tego celu potrzebne jest odpowiednie opakowanie ORM. - SDC
@SDC - problem ze standardami polega na tym, że istnieje więc Wiele z nich... - Alnitak
xkcd.com/927 - Lightness Races in Orbit
mysql_* funkcja to powłoka funkcji mysqlnd dla nowszych wersji PHP. Więc nawet jeśli stara biblioteka klienta nie jest już obsługiwana, mysqlnd jest utrzymywany :) - hakre


Mówiąc o techniczny powodów, jest tylko kilka, wyjątkowo konkretnych i rzadko używanych. Najprawdopodobniej nigdy nie użyjesz ich w swoim życiu.
Być może jestem zbyt nieświadomy, ale nigdy nie miałem okazji wykorzystać ich rzeczy jak

  • nieblokujące, asynchroniczne zapytania
  • procedury przechowywane zwracające wiele zestawów wyników
  • Szyfrowanie (SSL)
  • Kompresja

Jeśli ich potrzebujesz - to niewątpliwie techniczne powody, by odejść od rozszerzenia mysql w kierunku czegoś bardziej stylowego i nowoczesnego.

Niemniej jednak istnieją również problemy o charakterze nietechnicznym, które mogą sprawić, że twoje doświadczenie będzie nieco trudniejsze

  • dalsze korzystanie z tych funkcji w nowoczesnych wersjach PHP podniesie przestarzałe uwagi. Można je po prostu wyłączyć.
  • w odległej przyszłości mogą zostać usunięte z domyślnej wersji PHP. Nie jest to zbyt wielka sprawa, ponieważ mydsql ext zostanie przeniesiony do PECL, a każdy hoster z przyjemnością skompiluje z nim PHP, ponieważ nie chcą oni stracić klientów, których strony działały przez dziesięciolecia.
  • silny opór ze strony społeczności Stackoverflow. Еverytime wspomniałeś o tych uczciwych funkcjach, kiedy powiedziano ci, że są pod ścisłym tematem tabu.
  • będąc przeciętnym użytkownikiem PHP, najprawdopodobniej twój pomysł na wykorzystanie tych funkcji jest podatny na błędy i jest błędny. Właśnie z powodu tych wszystkich licznych tutoriali i podręczników, które uczą cię w niewłaściwy sposób. Nie same funkcje - muszę to podkreślić - ale sposób, w jaki są używane.

Ta ostatnia kwestia jest problemem.
Ale, moim zdaniem, proponowane rozwiązanie również nie jest lepsze.
Wydaje mi się zbyt idealistyczny Marzenie, że wszyscy ci użytkownicy PHP będą uczyć się poprawnie obsługiwać zapytania SQL naraz. Najprawdopodobniej po prostu zmieniliby mysql_ * na mysqli_ * mechanicznie, pozostawiając podejście takie samo. Zwłaszcza, że ​​mysqli czyni przygotowane oświadczenia z użyciem niesamowicie bolesnych i kłopotliwych.
Nie wspominając o tym ojczysty przygotowane oświadczenia nie są wystarczające do ochrony od SQL injection, a ani mysqli, ani PDO nie oferuje rozwiązania.

Zamiast więc walczyć z tym uczciwym przedłużeniem, wolałbym walczyć z niewłaściwymi praktykami i edukować ludzi we właściwy sposób.

Ponadto istnieją pewne fałszywe lub nieistotne przyczyny, takie jak

  • Nie obsługuje procedur składowanych (używaliśmy mysql_query("CALL my_proc"); na wieki)
  • Nie obsługuje transakcji (tak jak powyżej)
  • Nie obsługuje wielu instrukcji (kto ich potrzebuje?)
  • Nie pod aktywnym rozwojem (więc co to ma wpływ? ty w każdy praktyczny sposób?)
  • Brakuje interfejsu OO (utworzenie go to kwestia kilku godzin)
  • Nie obsługuje Przygotowanych stwierdzeń ani Parametryzowanych zapytań

Ten ostatni jest interesującym punktem. Chociaż mysql ext nie obsługuje ojczysty przygotowane oświadczenia, nie są wymagane dla bezpieczeństwa. Możemy łatwo fałszować przygotowane instrukcje za pomocą ręcznie obsługiwanych symboli zastępczych (tak jak robi to 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);

voilawszystko jest sparametryzowane i bezpieczne.

Ale dobrze, jeśli nie podoba ci się czerwone pole w instrukcji, pojawia się problem wyboru: mysqli lub PDO?

Odpowiedź brzmi:

  • Jeśli rozumiesz konieczność używania a warstwa abstrakcji bazy danych i szukasz API do stworzenia, mysqli jest bardzo dobrym wyborem, ponieważ obsługuje wiele funkcji specyficznych dla mysql.
  • Jeśli, jak większość użytkowników PHP, używasz surowych wywołań API bezpośrednio w kodzie aplikacji (co jest zasadniczo niewłaściwą praktyką) - CHNP jest jedynym wyborem, ponieważ to rozszerzenie udaje nie tylko API, ale raczej pół-DAL, wciąż niekompletne, ale oferuje wiele ważnych funkcji, z których dwa powodują, że PDO jest krytycznie odróżniane od mysqli:

    • w przeciwieństwie do mysqli, PDO może wiązać placeholder według wartości, dzięki któremu dynamicznie budowane zapytania są możliwe bez kilku ekranów dość niechlujnego kodu.
    • w przeciwieństwie do mysqli, PDO zawsze może zwrócić wynik zapytania w prostej zwykłej tablicy, podczas gdy mysqli może to zrobić tylko w instalacjach mysqlnd.

Tak więc, jeśli jesteś przeciętnym użytkownikiem PHP i chcesz zaoszczędzić sobie mnóstwa bólów głowy podczas używania natywnych przygotowanych stwierdzeń, PDO - znowu - jest jedynym wyborem.
Jednak PDO również nie jest srebrną kulą i ma swoje trudności.
Napisałem więc rozwiązania dla wszystkich typowych pułapek i skomplikowanych przypadków w Wiki tagów PDO

Niemniej jednak wszyscy mówiący o rozszerzeniach zawsze tęsknią za 2 ważne fakty o Mysqli i PDO:

  1. Przygotowane oświadczenie nie jest srebrną kulą. Istnieją dynamiczne identyfikatory, które nie mogą być związane za pomocą przygotowanych instrukcji. Istnieją dynamiczne zapytania o nieznanej liczbie parametrów, które powodują, że tworzenie zapytań jest trudnym zadaniem.

  2. W kodzie aplikacji nie powinny pojawić się funkcje mysqli_ * ani PDO.
    Powinien być warstwa abstrakcji między nimi a kodem aplikacji, który wykona całą brudną pracę związania, zapętlania, obsługi błędów itp. wewnątrz, dzięki czemu kod aplikacji będzie suchy i czysty. Zwłaszcza w przypadku złożonych przypadków, takich jak dynamiczne tworzenie zapytań.

Tak więc wystarczy przejść na PDO lub mysqli. Trzeba użyć ORM lub konstruktora zapytań, lub jakiejkolwiek klasy abstrakcji bazy danych, zamiast wywoływać surowe funkcje API w swoim kodzie.
I przeciwnie - jeśli masz warstwę abstrakcji między kodem aplikacji a mysql API - w rzeczywistości nie ma znaczenia, który silnik jest używany. Możesz używać mysql ext, aż stanie się przestarzałe, a następnie łatwo przerobić klasę abstrakcji na inny silnik, mając cały kod aplikacji nienaruszony.

Oto kilka przykładów opartych na moim Klasa safemysql aby pokazać, jak powinna wyglądać taka klasa abstrakcji:

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

Porównaj tę jedną linię z ilość kodu potrzebna w PDO.
Następnie porównaj z szalona ilość kodu będziesz potrzebować surowych instrukcji przygotowanych przez Mysqli. Pamiętaj, że obsługa błędów, profilowanie, rejestrowanie zapytań są już wbudowane i uruchomione.

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

Porównaj go ze zwykłymi wstawkami PDO, gdy każda nazwa pola jest powtarzana od sześciu do dziesięciu razy - we wszystkich tych nazwach, powiązaniach i definicjach zapytań o wielu nazwach.

Inny przykład:

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

Trudno znaleźć przykład dla PDO, który poradziłby sobie z takim praktycznym przypadkiem.
I będzie zbyt rozwlekły i najprawdopodobniej niebezpieczny.

Tak więc, po raz kolejny - to nie tylko surowy sterownik powinien być twoją troską, ale także klasą abstrakcji, przydatną nie tylko dla głupich przykładów z podręcznika dla początkujących, ale także dla rozwiązania prawdziwych problemów.


99
2017-10-12 13:23



mysql_* sprawia, że ​​luki w zabezpieczeniach są bardzo łatwe do zdobycia. Ponieważ PHP jest używane przez wielu początkujących użytkowników, mysql_* jest aktywnie szkodliwy w praktyce, nawet jeśli teoretycznie można go używać bez problemu. - Madara Uchiha♦
everything is parameterized and safe - może być sparametryzowany, ale twoja funkcja nie działa real przygotowane oświadczenia. - uınbɐɥs
Jak jest Not under active development tylko za to "0,01%"? Jeśli zbudujesz coś za pomocą tej funkcji stand-still, zaktualizuj swoją wersję mysql w ciągu roku i skończyć z niedziałającym systemem, jestem pewien, że w tym '0,01%' jest nagle bardzo dużo ludzi. Powiedziałbym to deprecated i not under active development są ściśle powiązane. Można powiedzieć, że nie ma "uzasadnienia", ale faktem jest, że gdy zaoferowano mu wybór między opcjami, no active development jest prawie tak samo zły jak deprecated Powiedziałbym? - Nanne
@MadaraUchiha: Czy możesz wyjaśnić, jak łatwo można uzyskać podatność na ataki? Zwłaszcza w przypadkach, gdy te same luki nie mają wpływu na PDO lub MySQLi ... Ponieważ nie jestem świadomy ani jednego, o którym mówisz. - ircmaxell
@ShaquinTrifonoff: oczywiście, nie używa przygotowanych instrukcji. Ale PDO również nie, które większość ludzi poleca nad MySQLi. Nie jestem pewien, czy ma to znaczący wpływ. Powyższy kod (z nieco dłuższą analizą) jest tym, co robi PDO, gdy domyślnie przygotowujesz instrukcję ... - ircmaxell


Istnieje wiele powodów, ale być może najważniejsze jest to, że te funkcje zachęcają do niestabilnych praktyk programistycznych, ponieważ nie obsługują przygotowanych oświadczeń. Przygotowane instrukcje pomagają zapobiegać atakom SQL injection.

Podczas używania mysql_* funkcje, musisz pamiętać, aby uruchamiać parametry dostarczane przez użytkownika mysql_real_escape_string(). Jeśli zapomnisz w jednym miejscu lub zdarzy ci się uciec tylko część danych wejściowych, Twoja baza danych może być przedmiotem ataku.

Używanie przygotowanych instrukcji w PDO lub mysqli sprawi, że tego rodzaju błędy programowania będą trudniejsze do wykonania.


88
2017-10-12 13:24



Niestety słabe wsparcie w MySQLi_ * dla przekazywania zmiennej liczby parametrów (np. Kiedy chcemy przekazać listę wartości do sprawdzenia w klauzuli IN) zachęca do niestosowania parametrów, zachęcając do stosowania dokładnie tych samych połączonych zapytań, które pozostawić podatne na połączenia MySQL_ *. - Kickstart
Ale, po raz kolejny, niepewność nie jest nieodłącznym problemem funkcji mysql_ *, ale problemem nieprawidłowego użycia. - Agamemnus
@Agamemnus Problem polega na tym, że mysql_ * ułatwia wdrożenie tego "nieprawidłowego użycia", szczególnie w przypadku niedoświadczonych programistów. Biblioteki, które implementują przygotowane instrukcje, utrudniają popełnienie tego typu błędów. - Trott


Ponieważ (między innymi) znacznie trudniej jest zapewnić bezpieczeństwo danych wejściowych. Jeśli korzystasz z zapytań sparametryzowanych, podobnie jak w przypadku PDO lub mysqli możesz całkowicie uniknąć ryzyka.

Na przykład ktoś może skorzystać "enhzflep); drop table users" jako nazwa użytkownika. Stare funkcje umożliwią wykonywanie wielu instrukcji na zapytanie, więc coś takiego paskudnego robaka może usunąć całą tabelę.

Gdyby używano PDO mysqli, nazwa użytkownika byłaby końcowa "enhzflep); drop table users".

Widzieć bobby-tables.com.


71
2017-09-18 12:28



The old functions will allow executing of multiple statements per query - Nie, nie zrobią tego. Ten rodzaj wtrysku nie jest możliwy w przypadku ext / mysql - jedyny sposób, w jaki ten rodzaj wtrysku jest możliwy przy użyciu PHP i MySQL podczas korzystania z MySQLi i mysqli_multi_query() funkcjonować. Rodzaj wtrysku, który jest możliwy w przypadku ext / mysql i unescaped stringów jest taki, jak ' OR '1' = '1 wyodrębnić dane z bazy danych, które nie miały być dostępne. W niektórych sytuacjach możliwe jest wstrzykiwanie podrzędnych zapytań, jednak wciąż nie jest możliwa modyfikacja bazy danych w ten sposób. - DaveRandom


Ta odpowiedź została napisana, aby pokazać, jak banalnie jest pominąć źle napisany kod sprawdzania poprawności PHP, jak (i ​​przy użyciu czego) te ataki działają i jak zastąpić stare funkcje MySQL bezpieczną przygotowaną instrukcją - i po prostu, dlaczego użytkownicy StackOverflow (prawdopodobnie z dużą ilością przedstawicieli) szczekają na nowych użytkowników, którzy zadają pytania, aby ulepszyć swój kod.

Po pierwsze, proszę utworzyć tę testową bazę danych mysql (nazwałem prep kopalni):

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)

Po wykonaniu tego możemy przejść do naszego kodu PHP.

Załóżmy, że poniższy skrypt to proces weryfikacji dla administratora na stronie internetowej (uproszczony, ale działający, jeśli skopiujesz go i użyjesz do testowania):

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

Na pierwszy rzut oka wydaje się wystarczająco legit.

Użytkownik musi wprowadzić login i hasło, prawda?

Świetne, nie należy wpisywać:

user: bob
pass: somePass

i prześlij to.

Dane wyjściowe są następujące:

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

Wspaniały! Działaj zgodnie z oczekiwaniami, teraz wypróbuj rzeczywistą nazwę użytkownika i hasło:

user: Fluffeh
pass: mypass

Niesamowity! Przez całą piątkę, kod poprawnie zweryfikował administratora. Jest idealny!

No nie bardzo. Powiedzmy, że użytkownik jest sprytną małą osobą. Powiedzmy, że ta osoba to ja.

Wpisz następujące informacje:

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

A wynik to:

The check passed. We have a verified admin!

Gratulacje! Po prostu pozwoliłeś mi wejść do sekcji super-chronionych administratorów tylko po wprowadzeniu fałszywej nazwy użytkownika i fałszywego hasła. Poważnie, jeśli mi nie wierzysz, utwórz bazę danych z podanym przeze mnie kodem i uruchom ten kod PHP - który na pierwszy rzut oka wydaje się raczej sprawnie zweryfikować nazwę użytkownika i hasło.

Tak więc, w odpowiedzi, TO DLACZEGO WARTO WYBRAĆ.

Zobaczmy więc, co poszło nie tak i dlaczego właśnie dostałem się do twojej super-administratora-tylko-jaskini-nietoperza. Zgadywałem i założyłem, że nie jesteś ostrożny ze swoimi danymi wejściowymi i po prostu przekazałeś je bezpośrednio do bazy danych. Skonstruowałem dane wejściowe w taki sposób, aby ZMIENIĆ zapytanie, które faktycznie było uruchomione. A więc, co miało być i czym się kończyło?

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

To jest zapytanie, ale kiedy zastąpimy zmienne rzeczywistymi danymi wejściowymi, które wykorzystaliśmy, otrzymujemy:

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

Zobacz, jak skonstruowałem swoje "hasło", aby najpierw zamknąć pojedynczy cytat dotyczący hasła, a następnie wprowadzić zupełnie nowe porównanie? Następnie dla bezpieczeństwa dodałem kolejny "ciąg", aby pojedynczy cytat został zamknięty zgodnie z oczekiwaniami w kodzie, który pierwotnie mieliśmy.

Nie chodzi jednak o to, że ludzie już na ciebie krzyczą, chodzi o to, by pokazać, jak sprawić, by Twój kod był bezpieczniejszy.

Okay, więc co poszło nie tak i jak możemy to naprawić?

Jest to klasyczny atak SQL injection. Jeden z najprostszych w tej kwestii. Na skali wektorów ataku jest to maluch atakujący czołg - i wygrywający.

Jak więc chronić naszą świętą administrację i czynić ją ładną i bezpieczną? Pierwszą rzeczą do zrobienia będzie zaprzestanie używania tych naprawdę starych i przestarzałych mysql_* Funkcje. Wiem, że poszłaś za tutorialem, który znalazłeś online i działa, ale jest stary, jest przestarzały iw ciągu kilku minut właśnie przebiegłam obok niego, nie łamiąc nawet potu.

Teraz masz lepsze możliwości użycia mysqli_ lub CHNP. Jestem osobiście wielkim fanem ChNP, więc będę używał ChNP w dalszej części tej odpowiedzi. Są zawodowcy i przeciwnicy, ale osobiście uważam, że zawodowcy znacznie przewyższają przeciwników. Jest przenośny w wielu silnikach baz danych - bez względu na to, czy korzystasz z MySQL, czy z Oracle, czy tylko z krwawych rzeczy - tylko zmieniając ciąg połączenia, ma wszystkie funkcje, których chcemy używać i jest ładny i czysty. Lubię czystość.

Teraz spójrzmy na ten kod ponownie, tym razem napisany przy użyciu obiektu 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>

Główne różnice polegają na tym, że nie ma ich więcej mysql_* Funkcje. Wszystko odbywa się za pośrednictwem obiektu PDO, po drugie, używa przygotowanej instrukcji. A teraz, o co pytasz? Jest to sposób na poinformowanie bazy danych przed uruchomieniem zapytania, jakie jest zapytanie, które będziemy uruchamiać. W tym przypadku informujemy bazę danych: "Cześć, mam zamiar uruchomić instrukcję select, która chce identyfikatora, userid i przekazać od użytkowników tabeli, gdzie userid jest zmienną, a pass jest również zmienną.".

Następnie, w instrukcji execute, przekazujemy bazę danych tablicę ze wszystkimi zmiennymi, których teraz oczekuje.

Wyniki są fantastyczne. Ponownie wypróbuj te kombinacje nazwy użytkownika i hasła:

user: bob
pass: somePass

Użytkownik nie został zweryfikowany. Niesamowite.

Co powiesz na:

user: Fluffeh
pass: mypass

Och, trochę się podnieciłem, zadziałało: Czek minął. Mamy zweryfikowanego administratora!

Teraz spróbujmy danych, które sprytny facet wprowadzi, aby spróbować przejść przez nasz mały system weryfikacji:

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

Tym razem otrzymujemy:

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

To dlatego jesteś krzyczany przy wysyłaniu pytań - to dlatego, że ludzie widzą, że Twój kod może zostać ominięty, nawet jeśli próbujesz. Proszę użyć tego pytania i odpowiedzi, aby ulepszyć swój kod, aby był bardziej bezpieczny i aby korzystać z bieżących funkcji.

Wreszcie, nie oznacza to, że jest to PERFEKCYJNY kod. Istnieje wiele innych rzeczy, które możesz zrobić, aby ją poprawić, użyj na przykład hashed passów, upewnij się, że podczas przechowywania senssetive informacji w bazie danych, nie przechowuj go w postaci zwykłego tekstu, mają wiele poziomów weryfikacji - ale tak naprawdę, jeśli po prostu zmieniasz swój stary kod podatny na wstrzyknięcia na to, będziesz DOBRZE w trakcie pisania dobrego kodu - a fakt, że dostałeś się tak daleko i wciąż czytasz daje mi poczucie nadziei, że nie tylko zaimplementujesz ten typ kodu podczas pisania twoich stron internetowych i aplikacji, ale możesz wyjść i zbadać te inne rzeczy, o których wspomniałem - i wiele więcej. Napisz najlepszy kod, jaki możesz, a nie najbardziej podstawowy kod, który ledwo działa.


63
2017-09-02 07:20



Dziękuję za Twoją odpowiedź! Niech moja +1! Warto to zauważyć mysql_* samo w sobie nie jest niebezpieczne, ale promuje niezabezpieczony kod za pomocą złych samouczków i braku odpowiedniego oświadczenia przygotowania API. - Madara Uchiha♦
unhashed hasła, o horror! = oP W przeciwnym razie +1 dla szczegółowego wyjaśnienia. - cryptic ツ


Rozszerzenie MySQL jest najstarszym z trzech i było oryginalnym sposobem, w jaki programiści używali do komunikacji z MySQL. To rozszerzenie jest teraz przestarzałe na korzyść drugiej dwa  alternatywy z powodu ulepszeń wprowadzonych w nowszych wersjach PHP i MySQL.

  • MySQLi jest "ulepszonym" rozszerzeniem do pracy z bazami danych MySQL. Korzysta z funkcji dostępnych w nowszych wersjach serwera MySQL, udostępnia programistom zarówno interfejs zorientowany na funkcje, jak i obiektowy, a także robi kilka innych fajnych rzeczy.

  • CHNP oferuje API, które konsoliduje większość funkcji, które wcześniej były rozproszone w głównych rozszerzeniach dostępu do baz danych, tj. MySQL, PostgreSQL, SQLite, MSSQL, itp. Interfejs udostępnia obiekty wysokiego poziomu dla programisty do pracy z połączeniami, zapytaniami i wynikiem bazy danych zestawy, a sterowniki niskopoziomowe obsługują komunikację i obsługę zasobów za pomocą serwera bazy danych. Wiele dyskusji i prac przechodzi na PDO i jest to odpowiednia metoda pracy z bazami danych w nowoczesnym, profesjonalnym kodzie.


31
2017-09-07 15:06





Uważam powyższe odpowiedzi za naprawdę długie, więc podsumowując:

Rozszerzenie mysqli ma pewną liczbę   korzyści, kluczowe ulepszenia ponad   rozszerzenie mysql to:

  • Interfejs obiektowy
  • Obsługa przygotowanych sprawozdań
  • Obsługa wielu wyciągów
  • Wsparcie dla transakcji
  • Rozszerzone możliwości debugowania
  • Obsługa wbudowanego serwera

Źródło: Omówienie MySQLi


Jak wyjaśniono w powyższych odpowiedziach, alternatywy dla mysql to mysqli i PDO (PHP Data Objects).

  • API obsługuje instrukcje przygotowane po stronie serwera: Obsługiwane przez MYSQLi i PDO
  • API obsługuje instrukcje przygotowane po stronie klienta: obsługiwane tylko przez PDO
  • Interfejs API obsługuje procedury przechowywane: zarówno MySQLi, jak i PDO
  • API obsługuje wiele instrukcji i wszystkie funkcje MySQL 4.1+ - obsługiwane przez MySQLi, a przede wszystkim przez PDO

Zarówno MySQLi, jak i PDO zostały wprowadzone w PHP 5.0, natomiast MySQL wprowadzono przed PHP 3.0. Należy zauważyć, że MySQL jest zawarty w PHP5.x, ale przestarzały w późniejszych wersjach.


20
2018-06-09 06:24



Twoja odpowiedź jest zbyt długa, a prawdziwe podsumowanie to "mysql ext już nie". To wszystko - Your Common Sense
@ YourCommonSense Moja odpowiedź brzmi, dlaczego mysqli zastąpił mysql. Nie znaczy to, że Mysqli istnieje dzisiaj, więc używaj go. Każdy to wie! - Ani Menon
Cóż, pomijając fakt, że nikt nie zapytał, dlaczego mysqli zastąpił mysql, nie odpowiada również na to pytanie. Odpowiada, dlaczego wprowadzono mysqli. Ale to nie wyjaśnia, dlaczego mysql i mysqli nie mogą mieszkać równolegle - Your Common Sense
@ YourCommonSense Również pytanie OP brzmi: "Dlaczego powinienem używać czegoś innego, nawet jeśli działają na mojej stronie?" i właśnie dlatego wskazałem zmiany i ulepszenia. Możesz spojrzeć na wszystkie inne odpowiedzi, które są długie, więc pomyślałem, że powinienem to podsumować. - Ani Menon