Perché non dovrei usare le funzioni mysql_ * in PHP?


2502

Quali sono i motivi tecnici per cui non si dovrebbero usare le mysql_*funzioni? (ad es mysql_query(). mysql_connect()oppure mysql_real_escape_string())?

Perché dovrei usare qualcos'altro anche se funzionano sul mio sito?

Se non funzionano sul mio sito, perché visualizzo errori come

Avviso: mysql_connect (): nessun file o directory


Errore simile a: Errore irreversibile: errore non rilevato: chiamata alla funzione indefinita mysql_connect () ...
Bimal Poudel

21
Deprecato da solo è una ragione sufficiente per evitarli
Sasa1234,

Risposte:


2089

L'estensione MySQL:

  • Non è in fase di sviluppo attivo
  • È ufficialmente deprecato a partire da PHP 5.5 (rilasciato a giugno 2013).
  • È stato rimosso interamente a partire da PHP 7.0 (rilasciato a dicembre 2015)
    • Ciò significa che al 31 dicembre 2018 non esiste in nessuna versione supportata di PHP. Se stai usando una versione di PHP che la supporta, stai usando una versione che non risolve i problemi di sicurezza.
  • Manca un'interfaccia OO
  • Non supporta:
    • Query asincrone e non bloccanti
    • Dichiarazioni preparate o query con parametri
    • Procedura di archiviazione
    • Dichiarazioni multiple
    • Le transazioni
    • Il "nuovo" metodo di autenticazione della password (attivo di default in MySQL 5.6; richiesto in 5.7)
    • Tutte le nuove funzionalità di MySQL 5.1 o successive

Dal momento che è deprecato, l'utilizzo rende il codice meno a prova di futuro.

La mancanza di supporto per le dichiarazioni preparate è particolarmente importante in quanto forniscono un metodo più chiaro e meno soggetto a errori di fuga e quotazione di dati esterni rispetto alla fuga manuale con una chiamata di funzione separata.

Vedi il confronto delle estensioni SQL .


287
Deprecato da solo è una ragione sufficiente per evitarli. Non ci saranno un giorno e non sarai felice se ti affidi a loro. Il resto è solo un elenco di cose che l'uso delle vecchie estensioni ha impedito alle persone di apprendere.
Tim Post

111
La deprecazione non è il proiettile magico che tutti sembrano pensare che lo sia. PHP stesso non ci sarà un giorno, eppure facciamo affidamento sugli strumenti di cui disponiamo oggi. Quando dovremo cambiare strumenti, lo faremo.
Razze di leggerezza in orbita

133
@LightnessRacesinOrbit - La deprecazione non è un proiettile magico, è una bandiera che dice "Riconosciamo che fa schifo, quindi non lo sosterremo più a lungo". Sebbene avere una migliore correzione futura del codice sia una buona ragione per allontanarsi dalle funzionalità deprecate, non è l'unico (o anche quello principale). Cambia strumenti perché ci sono strumenti migliori, non perché sei obbligato a farlo. (E cambiare gli strumenti prima di essere costretti a farlo significa che non stai imparando quelli nuovi solo perché il tuo codice ha smesso di funzionare e ha bisogno di essere riparato ieri ... che è il momento peggiore per imparare nuovi strumenti).
Quentin,

18
Una cosa che non ho visto menzionato sulla mancanza di dichiarazioni preparate è il problema delle prestazioni. Ogni volta che emetti una dichiarazione, qualcosa deve compilarla in modo che il demone MySQL possa capirla. Con questa API, se si emettono 200.000 della stessa query in un ciclo, questo è 200.000 volte che la query deve essere compilata affinché MySQL possa comprenderla. Con le istruzioni preparate, viene compilato una volta, quindi i valori vengono parametrizzati nell'SQL compilato.
Goldentoa11,

20
@symcbean, Sicuramente non supporta dichiarazioni preparate. Questo è in effetti il ​​motivo principale per cui è deprecato. Senza istruzioni preparate (facili da usare), l'estensione mysql è spesso vittima di attacchi di iniezione SQL.
Rustyx,

1287

PHP offre tre diverse API per connettersi a MySQL. Queste sono le mysql(rimosse da PHP 7) mysqlie le PDOestensioni.

Le mysql_*funzioni erano molto popolari, ma il loro uso non è più incoraggiato. Il team di documentazione sta discutendo della situazione della sicurezza del database e istruendo gli utenti ad allontanarsi dall'estensione ext / mysql comunemente usata fa parte di questo (consultare php.internals: deprecating ext / mysql ).

E il team sviluppatore PHP in seguito ha preso la decisione per generare E_DEPRECATEDerrori quando gli utenti si connettono a MySQL, sia attraverso mysql_connect(), mysql_pconnect()o la funzionalità di connessione implicita integrato in ext/mysql.

ext/mysqlè stato ufficialmente deprecato a partire da PHP 5.5 ed è stato rimosso a partire da PHP 7 .

Vedi la scatola rossa?

Quando vai su qualsiasi mysql_*pagina del manuale delle funzioni, viene visualizzata una casella rossa che spiega che non dovrebbe più essere utilizzata.

Perché


Allontanarsi ext/mysqlnon riguarda solo la sicurezza, ma anche l'accesso a tutte le funzionalità del database MySQL.

ext/mysqlè stato creato per MySQL 3.23 e da allora ha ottenuto pochissime aggiunte mantenendo per lo più la compatibilità con questa vecchia versione che rende il codice un po 'più difficile da mantenere. Le funzioni mancanti non supportate da ext/mysqlincludono: ( dal manuale di PHP ).

Motivo per non utilizzare la mysql_*funzione :

  • Non in fase di sviluppo attivo
  • Rimosso da PHP 7
  • Manca un'interfaccia OO
  • Non supporta query asincrone e non bloccanti
  • Non supporta istruzioni preparate o query con parametri
  • Non supporta le stored procedure
  • Non supporta più istruzioni
  • Non supporta le transazioni
  • Non supporta tutte le funzionalità di MySQL 5.1

Sopra il punto citato dalla risposta di Quentin

La mancanza di supporto per le dichiarazioni preparate è particolarmente importante in quanto forniscono un metodo più chiaro e meno soggetto a errori di fuga e quotazione di dati esterni rispetto alla fuga manuale con una chiamata di funzione separata.

Vedi il confronto delle estensioni SQL .


Soppressione degli avvisi di deprecazione

Durante la conversione del codice in MySQLi/ PDO, è E_DEPRECATEDpossibile eliminare gli errori impostando error_reportingin php.ini per escludereE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Si noti che questo nasconderà anche altri avvisi di deprecazione , che tuttavia potrebbero riguardare cose diverse da MySQL. ( dal manuale di PHP )

L'articolo DOP vs MySQLi: quale dovresti usare? di Dejan Marjanovic ti aiuterà a scegliere.

E un modo migliore è PDO, e ora sto scrivendo un semplice PDOtutorial.


Un tutorial PDO semplice e breve


D. La prima domanda che avevo in mente era: che cos'è il DOP?

R. " DOP - PHP Data Objects - è un livello di accesso al database che fornisce un metodo uniforme di accesso a più database".

testo alternativo


Connessione a MySQL

Con la mysql_*funzione o possiamo dirlo alla vecchia maniera (deprecato in PHP 5.5 e versioni successive)

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

Con PDO: Tutto quello che devi fare è creare un nuovo PDOoggetto. Il costruttore accetta parametri per specificare l'origine di database PDO's costruttore prende principalmente quattro parametri che sono DSN(nome di origine dati) e opzionalmente username, password.

Qui penso che tu abbia familiarità con tutti tranne DSN; questo è nuovo in PDO. A DSNè fondamentalmente una serie di opzioni che indicano PDOquale driver utilizzare e i dettagli di connessione. Per ulteriori riferimenti, consultare il DSN MySQL DOP .

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

Nota: puoi anche usare charset=UTF-8, ma a volte provoca un errore, quindi è meglio usarlo utf8.

Se si verifica un errore di connessione, verrà generato un PDOExceptionoggetto che può essere catturato per essere gestito Exceptionulteriormente.

Buona lettura : connessioni e gestione delle connessioni ¶

È inoltre possibile passare diverse opzioni del driver come array al quarto parametro. Consiglio di passare il parametro che mette PDOin modalità eccezione. Poiché alcuni PDOdriver non supportano le istruzioni preparate native, quindi PDOesegue l'emulazione del preparato. Inoltre, consente di abilitare manualmente questa emulazione. Per utilizzare le istruzioni preparate sul lato server nativo, è necessario impostarle in modo esplicito false.

L'altro è disattivare preparare l'emulazione che è abilitata nel MySQLdriver per impostazione predefinita, ma preparare l'emulazione deve essere disattivata per un utilizzo PDOsicuro.

In seguito spiegherò perché preparare l'emulazione dovrebbe essere disattivato. Per trovare la ragione, controlla questo post .

È utilizzabile solo se si utilizza una versione precedente di MySQLcui non è consigliabile.

Di seguito è riportato un esempio di come è possibile farlo:

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

Possiamo impostare gli attributi dopo la costruzione DOP?

, possiamo anche impostare alcuni attributi dopo la costruzione DOP con il setAttributemetodo:

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

Gestione degli errori


La gestione degli errori è molto più semplice PDOdi mysql_*.

Una pratica comune durante l'utilizzo mysql_*è:

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

OR die()non è un buon modo per gestire l'errore poiché non possiamo gestire la cosa die. Terminerà bruscamente lo script e poi farà eco all'errore sullo schermo che di solito NON vuoi mostrare ai tuoi utenti finali, e permetterà agli hacker insanguinati di scoprire il tuo schema. In alternativa, i valori di ritorno delle mysql_*funzioni possono spesso essere utilizzati insieme a mysql_error () per gestire gli errori.

PDOoffre una soluzione migliore: eccezioni. Qualsiasi cosa facciamo con PDOdovrebbe essere avvolto in un try- catchblocco. Possiamo forzare PDOin una delle tre modalità di errore impostando l'attributo della modalità di errore. Di seguito sono riportate tre modalità di gestione degli errori.

  • PDO::ERRMODE_SILENT. Sta solo impostando i codici di errore e agisce in modo analogo a quello in mysql_*cui è necessario controllare ogni risultato e quindi guardare $db->errorInfo();per ottenere i dettagli dell'errore.
  • PDO::ERRMODE_WARNINGAlza E_WARNING. (Avvisi di runtime (errori non fatali). L'esecuzione dello script non viene interrotta.)
  • PDO::ERRMODE_EXCEPTION: Genera eccezioni. Rappresenta un errore generato da DOP. Non si dovrebbe lanciare a PDOExceptiondal proprio codice. Vedi Eccezioni per ulteriori informazioni sulle eccezioni in PHP. Si comporta in modo molto simile a or die(mysql_error());quando non viene catturato. Ma a differenza di or die(), PDOExceptionpuò essere catturato e gestito con grazia se si sceglie di farlo.

Buona lettura :

Piace:

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

E si può avvolgere in try- catch, come di seguito:

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

Non devi occuparti di try- in catchquesto momento. Puoi prenderlo in qualsiasi momento appropriato, ma ti consiglio vivamente di usare try- catch. Inoltre potrebbe avere più senso prenderlo all'esterno della funzione che chiama PDOroba:

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

Inoltre, puoi gestirlo or die()o possiamo dire come mysql_*, ma sarà davvero vario. È possibile nascondere i messaggi di errore pericolosi in produzione girando display_errors offe leggendo semplicemente il registro degli errori.

Ora, dopo aver letto tutte le cose di lassù, probabilmente stai pensando: cosa diavolo è che quando voglio solo iniziare appoggiato semplici SELECT, INSERT, UPDATE, o DELETEaffermazioni? Non ti preoccupare, eccoci qui:


Selezione dei dati

Immagine di selezione DOP

Quindi quello che stai facendo 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'];
}

Ora in PDO, puoi farlo come:

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

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

O

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

//Use $results

Nota : se si utilizza il metodo come indicato di seguito ( query()), questo metodo restituisce un PDOStatementoggetto. Quindi, se vuoi recuperare il risultato, usalo come sopra.

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

In Dati DOP, si ottiene tramite il ->fetch()metodo di gestione dell'istruzione. Prima di chiamare il recupero, l'approccio migliore sarebbe dire a DOP come desideri che i dati vengano recuperati. Nella sezione seguente sto spiegando questo.

Modalità di recupero

Nota l'uso di PDO::FETCH_ASSOCnel fetch()e fetchAll()codice sopra. Questo dice PDOdi restituire le righe come un array associativo con i nomi dei campi come chiavi. Ci sono anche molte altre modalità di recupero che spiegherò una per una.

Prima di tutto, spiego come selezionare la modalità di recupero:

 $stmt->fetch(PDO::FETCH_ASSOC)

In quanto sopra, ho usato fetch(). Puoi anche usare:

Ora vengo in modalità di recupero:

  • PDO::FETCH_ASSOC: restituisce un array indicizzato dal nome della colonna come restituito nel set di risultati
  • PDO::FETCH_BOTH (impostazione predefinita): restituisce un array indicizzato dal nome della colonna e dal numero della colonna con indice 0 restituito nel set di risultati

Ci sono ancora più scelte! Leggi tutto su di loro nella PDOStatementdocumentazione di recupero. .

Ottenere il conteggio delle righe :

Invece di utilizzare mysql_num_rowsper ottenere il numero di righe restituite, è possibile ottenere un PDOStatemente fare rowCount(), come:

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

Ottenere l'ultimo ID inserito

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

Inserire e aggiornare o eliminare le istruzioni

Inserisci e aggiorna l'immagine DOP

Quello che stiamo facendo in mysql_*funzione è:

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

E in pdo, questa stessa cosa può essere fatta da:

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

Nella query precedente PDO::execeseguire un'istruzione SQL e restituire il numero di righe interessate.

Inserisci ed elimina saranno trattati in seguito.

Il metodo sopra è utile solo quando non si utilizza la variabile nella query. Ma quando è necessario utilizzare una variabile in una query, non provare mai come sopra e lì è per un'istruzione preparata o un'istruzione con parametri .


Dichiarazioni preparate

D. Che cos'è un'affermazione preparata e perché ne ho bisogno?
R. Un'istruzione preparata è un'istruzione SQL precompilata che può essere eseguita più volte inviando solo i dati al server.

Il flusso di lavoro tipico dell'utilizzo di un'istruzione preparata è il seguente ( citato da Wikipedia tre punti 3 ):

  1. Prepara : il modello di istruzione viene creato dall'applicazione e inviato al sistema di gestione del database (DBMS). Alcuni valori vengono lasciati non specificati, chiamati parametri, segnaposto o variabili di bind (etichettati di ?seguito):

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

  2. Il DBMS analizza, compila ed esegue l'ottimizzazione delle query sul modello di istruzione e memorizza il risultato senza eseguirlo.

  3. Esegui : in un secondo momento, l'applicazione fornisce (o associa) i valori per i parametri e il DBMS esegue l'istruzione (eventualmente restituendo un risultato). L'applicazione può eseguire l'istruzione tutte le volte che lo desidera con valori diversi. In questo esempio, potrebbe fornire "Bread" per il primo parametro e 1.00per il secondo parametro.

È possibile utilizzare un'istruzione preparata includendo segnaposto nel proprio SQL. Esistono sostanzialmente tre senza segnaposto (non provarlo con la variabile sopra quella), uno con segnaposto senza nome e uno con segnaposto denominato.

D. Quindi, ora, quali sono i segnaposti denominati e come li uso?
A. Segnaposti nominati. Usa nomi descrittivi preceduti da due punti, anziché da punti interrogativi. Non ci interessa la posizione / ordine di valore nel segnaposto nome:

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

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

Puoi anche eseguire il bind utilizzando anche un array di esecuzione:

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

Un'altra caratteristica interessante per gli OOPamici è che i segnaposto con nome hanno la possibilità di inserire oggetti direttamente nel database, assumendo che le proprietà corrispondano ai campi con nome. Per esempio:

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. Quindi ora, cosa sono i segnaposto senza nome e come li uso?
A. Facciamo un esempio:

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

e

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

In quanto sopra, puoi vedere quelli ?invece di un nome come in un segnaposto nome. Ora nel primo esempio, assegniamo le variabili ai vari segnaposto ( $stmt->bindValue(1, $name, PDO::PARAM_STR);). Quindi, assegniamo valori a quei segnaposto ed eseguiamo l'istruzione. Nel secondo esempio, il primo elemento dell'array passa al primo ?e il secondo al secondo ?.

NOTA : nei segnaposto senza nome dobbiamo occuparci del corretto ordine degli elementi nell'array che stiamo passando al PDOStatement::execute()metodo.


SELECT, INSERT, UPDATE, DELETEQuery preparate

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

NOTA:

Tuttavia PDOe / o MySQLinon sono completamente sicuri. Controlla la risposta Le istruzioni preparate per DOP sono sufficienti per prevenire l'iniezione di SQL? di ircmaxell . Inoltre, sto citando una parte della sua risposta:

$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
Ciò che la buona lettura sopra dovrebbe probabilmente menzionare: la dichiarazione preparata toglie qualsiasi uso significativo del IN (...) construct.
Eugen Rieck,

24
La domanda era "Perché non dovrei usare le funzioni mysql_ * in PHP". Questa risposta, sebbene impressionante e piena di informazioni utili, va ben oltre il campo di applicazione e come dice @trejder - 8 persone su 10 perderanno queste informazioni semplicemente perché non hanno 4 ore da dedicare cercando di lavorare esso. Ciò sarebbe molto più prezioso, suddiviso e utilizzato come risposta a diverse domande più precise.
Alex McMillan,

Preferibilmente preferisco mysqli e DOP. Ma per la gestione degli stampi, ho provato l'alternativa alle eccezioni function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();Funziona per lanciare eccezioni.
kuldeep.kamboj

elenchi Doesn't support non-blocking, asynchronous queriescome motivo per non usare mysql_ - dovresti anche elencarlo come motivo per non usare DOP, perché anche DOP non lo supporta. (ma MySQLi lo supporta)
hanshenrik,

è possibile utilizzare Charset utf8mb4_unicode_ci poiché ho un database che lo utilizza?
Ryan Stone,

301

Innanzitutto, cominciamo con il commento standard che diamo a tutti:

Per favore, non usare le mysql_*funzioni nel nuovo codice . Non sono più mantenuti e sono ufficialmente deprecati . Vedi la scatola rossa ? Scopriinvece le dichiarazioni preparate e usa PDO o MySQLi : questo articolo ti aiuterà a decidere quale. Se scegli DOP, ecco un buon tutorial .

Esaminiamo questa frase frase per frase e spieghiamo:

  • Non vengono più mantenuti e sono ufficialmente deprecati

    Ciò significa che la comunità PHP sta gradualmente abbandonando il supporto per queste funzioni molto vecchie. È probabile che non esistano in una futura (recente) versione di PHP! L'uso continuato di queste funzioni potrebbe violare il codice in (non molto) futuro.

    NUOVO! - ext / mysql è ora ufficialmente deprecato a partire da PHP 5.5!

    Più nuovo! ext / mysql è stato rimosso in PHP 7 .

  • Invece, dovresti conoscere le dichiarazioni preparate

    mysql_* l'estensione non supporta istruzioni preparate , che è (tra le altre cose) una contromisura molto efficace contro SQL Injection . Risolve una vulnerabilità molto grave nelle applicazioni dipendenti da MySQL che consente agli aggressori di ottenere l'accesso allo script ed eseguire qualsiasi query sul database.

    Per ulteriori informazioni, vedi Come posso impedire l'iniezione SQL in PHP?

  • Vedi la scatola rossa?

    Quando vai a qualsiasi mysqlpagina del manuale delle funzioni, viene visualizzata una casella rossa che spiega che non dovrebbe più essere utilizzata.

  • Utilizzare PDO o MySQLi

    Esistono alternative migliori, più solide e ben costruite, PDO - PHP Database Object , che offre un approccio OOP completo all'interazione con il database, e MySQLi , che è un miglioramento specifico di MySQL.


6
C'è ancora un'altra cosa: penso che la funzione esista ancora in PHP per un solo motivo: compatibilità con sistemi CMS, e-commerce, e-commerce, e-commerce, obsoleti, ma ancora in esecuzione, infine verrà rimosso e dovrai riscrivere il tuo applicazione ...
Kamil,

4
@Kamil: È vero, ma non è proprio un motivo per cui non dovresti usarlo. Il motivo per non usarlo è perché è antico, insicuro, ecc. :)
Madara's Ghost

4
@Mario - gli sviluppatori PHP hanno un processo e hanno appena votato a favore del deprezzamento formale di ext / mysql a partire da 5.5. Non è più un problema ipotetico.
DSC

2
L'aggiunta di un paio di righe extra con una tecnica collaudata come DOP o MySQLi offre ancora la facilità d'uso che PHP ha sempre offerto. Spero per lo sviluppatore che lui / lei sappia che vedere queste terribili funzioni mysql_ * in qualsiasi tutorial in realtà toglie la lezione e dovrebbe dire all'OP che questo tipo di codice è veramente 10 anni fa- e dovrebbe mettere in discussione il rilevanza anche del tutorial!
FredTheWebGuy

1
Ciò che la risposta dovrebbe eventualmente menzionare: una dichiarazione preparata toglie qualsiasi uso significativo del IN (...) construct.
Eugen Rieck,

217

Facilità d'uso

Le ragioni analitiche e sintetiche sono già state menzionate. Per i nuovi arrivati ​​c'è un incentivo più significativo a smettere di usare le funzioni mysql_ datate.

Le API del database contemporaneo sono semplicemente più facili da usare.

Sono soprattutto i parametri associati che possono semplificare il codice. E con eccellenti tutorial (come visto sopra) il passaggio a DOP non è eccessivamente arduo.

Riscrivere contemporaneamente una base di codice più grande richiede tempo. Raison d'être per questa alternativa intermedia:

Funzioni equivalenti pdo_ * al posto di mysql_ *

Usando < pdo_mysql.php > puoi passare dalle vecchie funzioni mysql_ con il minimo sforzo . Aggiunge pdo_wrapper di funzioni che sostituiscono le loro mysql_controparti.

  1. Semplicemente in ogni script di invocazione che deve interagire con il database. include_once("pdo_mysql.php");

  2. Rimuovere il mysql_prefisso di funzione ovunque e sostituirlo con pdo_.

    • mysql_connect() diventa pdo_connect()
    • mysql_query() diventa pdo_query()
    • mysql_num_rows() diventa pdo_num_rows()
    • mysql_insert_id() diventa pdo_insert_id()
    • mysql_fetch_array() diventa pdo_fetch_array()
    • mysql_fetch_assoc() diventa pdo_fetch_assoc()
    • mysql_real_escape_string() diventa pdo_real_escape_string()
    • e così via...

  3. Il tuo codice funzionerà allo stesso modo e continuerà ad avere lo stesso aspetto:

    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à.
Il tuo codice utilizza PDO.
Ora è il momento di utilizzarlo davvero .

I parametri associati possono essere facili da usare

Hai solo bisogno di un'API meno ingombrante.

pdo_query()aggiunge un supporto molto facile per i parametri associati. La conversione del vecchio codice è semplice:

Sposta le variabili fuori dalla stringa SQL.

  • Aggiungili come parametri di funzione delimitati da virgole a pdo_query().
  • Posiziona i punti interrogativi ?come segnaposto in cui erano prima le variabili.
  • Sbarazzarsi di 'virgolette singole che precedentemente racchiudevano valori / variabili stringa.

Il vantaggio diventa più evidente per un codice più lungo.

Spesso le variabili stringa non sono solo interpolate in SQL, ma concatenate con chiamate di escape in mezzo.

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

Con i ?segnaposto applicati non devi preoccuparti di questo:

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

Ricorda che pdo_ * consente ancora o .
Basta non sfuggire a una variabile e associarla nella stessa query.

  • La funzione segnaposto è fornita dal vero DOP dietro di essa.
  • Pertanto, anche gli :namedelenchi segnaposto consentiti in seguito.

Ancora più importante, puoi passare le variabili $ _REQUEST [] in modo sicuro dietro qualsiasi query. Quando i <form>campi inviati corrispondono esattamente alla struttura del database è anche più breve:

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

Tanta semplicità. Ma torniamo ad alcuni consigli di riscrittura e motivi tecnici sul motivo per cui potresti voler sbarazzarti mysql_e scappare.

Correggi o rimuovi qualsiasi sanitize()funzione oldschool

Dopo aver convertito tutte le mysql_chiamate in pdo_querycon parametri associati, rimuovere tutte le pdo_real_escape_stringchiamate ridondanti .

In particolare, dovresti correggere qualsiasi sanitizeo cleano filterThiso clean_datafunzioni come pubblicizzato dai tutorial datati in un modo o nell'altro:

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

Il bug più evidente qui è la mancanza di documentazione. Più significativamente, l'ordine del filtro era esattamente nell'ordine sbagliato.

  • L'ordine corretto sarebbe stato: deprecato stripslashescome la chiamata più interna, quindi trim, successivamente strip_tags, htmlentitiesper il contesto di output, e solo infine _escape_stringcome la sua applicazione dovrebbe precedere direttamente l'intersparsing SQL.

  • Ma come primo passo basta sbarazzarsi della_real_escape_string chiamata.

  • Potrebbe essere necessario mantenere il resto della sanitize()funzione per ora se il flusso del database e dell'applicazione prevede stringhe sicure per il contesto HTML. Aggiungi un commento che applica solo l'escaping HTML da ora in poi.

  • La gestione di stringhe / valori è delegata a PDO e alle sue istruzioni con parametri.

  • Se è stato menzionato stripslashes()nella funzione di sanitizzazione, potrebbe indicare una supervisione di livello superiore.

    Nota storica su magic_quotes. Questa funzione è giustamente deprecata. Tuttavia , spesso viene erroneamente descritto come funzionalità di sicurezza non riuscita . Ma i magic_quotes sono tanto una caratteristica di sicurezza fallita quanto le palline da tennis hanno fallito quanto la fonte nutrizionale. Semplicemente non era il loro scopo.

    L'implementazione originale in PHP2 / FI l'ha introdotta esplicitamente con solo "le virgolette verranno automaticamente sfuggite rendendo più semplice il passaggio dei dati del modulo direttamente alle query msql ". In particolare, è stato accidentalmente sicuro da usare con mSQL , poiché supportava solo ASCII.
    Quindi PHP3 / Zend hanno reintrodotto magic_quotes per MySQL e lo hanno documentato male. Ma originariamente era solo una funzione di convenienza , non destinata alla sicurezza.

In che modo le dichiarazioni preparate differiscono

Quando si inseriscono variabili stringa nelle query SQL, non è solo più complicato da seguire. È inoltre uno sforzo estraneo per MySQL di separare nuovamente codice e dati.

Le iniezioni di SQL sono semplicemente quando i dati sanguinano nel contesto del codice . Un server di database non può in seguito individuare dove originariamente PHP ha incollato le variabili tra le clausole di query.

Con i parametri associati separi i valori del codice SQL e del contesto SQL nel tuo codice PHP. Ma non viene mischiato di nuovo dietro le quinte (tranne che con DOP :: EMULATE_PREPARES). Il database riceve i comandi SQL invariati e i valori delle variabili 1: 1.

Mentre questa risposta sottolinea che dovresti preoccuparti dei vantaggi della leggibilità della caduta mysql_. Occasionalmente c'è anche un vantaggio in termini di prestazioni (ripetuti INSERT con valori solo diversi) a causa di questa separazione dei dati / codice visibile e tecnica.

Fai attenzione che il binding dei parametri non è ancora una soluzione magica one-stop contro tutte le iniezioni di SQL. Gestisce l'uso più comune di dati / valori. Ma non è possibile inserire nella whitelist nomi di colonne / identificatori di tabelle, aiutare nella costruzione di clausole dinamiche o semplicemente elenchi di valori di array semplici.

Uso ibrido DOP

Queste pdo_*funzioni wrapper creano un'API stop-gap intuitiva per la codifica. (È praticamente quello che MYSQLIavrebbe potuto essere se non fosse stato per il cambio di firma della funzione idiosincratica). Inoltre espongono il vero DOP nella maggior parte dei casi.
La riscrittura non deve smettere di usare i nuovi nomi di funzioni pdo_. Puoi passare uno a uno ogni pdo_query () in una semplice chiamata $ pdo-> prepar () -> execute ().

Tuttavia, è meglio ricominciare a semplificare. Ad esempio il recupero del risultato comune:

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

Può essere sostituito con solo un'iterazione foreach:

foreach ($result as $row) {

O meglio ancora un recupero diretto e completo dell'array:

$result->fetchAll();

Riceverai avvisi più utili nella maggior parte dei casi rispetto a PDO o mysql_ di solito dopo query non riuscite.

Altre opzioni

Quindi, si spera che abbia visualizzato alcune ragioni pratiche e un percorso degno di cadere mysql_.

Basta passare a non lo taglia del tutto. pdo_query()è anche solo un frontend su di esso.

A meno che non introduciate anche l'associazione di parametri o non sia possibile utilizzare qualcos'altro dall'API più gradevole, è un passaggio inutile. Spero che sia ritratto abbastanza semplice da non favorire lo scoraggiamento per i nuovi arrivati. (L'istruzione di solito funziona meglio del divieto.)

Mentre si qualifica per la categoria più semplice-che-potrebbe-possibilmente funzionare, è anche un codice molto sperimentale. L'ho appena scritto durante il fine settimana. Esistono tuttavia molte alternative. Cerca su Google l' astrazione del database PHP e naviga un po '. Ci sono sempre state e ci saranno molte librerie eccellenti per tali compiti.

Se vuoi semplificare ulteriormente l'interazione con il tuo database, vale la pena provare mapper come Paris / Idiorm . Proprio come nessuno usa più il blando DOM in JavaScript, al giorno d'oggi non è necessario fare da babysitter a un'interfaccia di database non elaborata.


8
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');
Fai

@Tom Certo, anche se non è mantenuto molto (0.9.2 era l'ultimo), puoi creare un account fossile , aggiungere al wiki o presentare una segnalazione di bug (senza registrazione IIRC).
Mario

pdo_real_escape_string() <- È anche una vera funzione, non riesco a trovare alcuna documentazione per esso? Si prega di pubblicare una fonte per questo.
Ryan Stone,

144

Le mysql_funzioni:

  1. non sono aggiornati - non vengono più mantenuti
  2. non ti consente di passare facilmente a un altro back-end del database
  3. non supportare dichiarazioni preparate, quindi
  4. incoraggiare i programmatori a utilizzare la concatenazione per creare query, portando a vulnerabilità nell'iniezione SQL

18
# 2 è ugualmente vero permysqli_
Eggyal

16
per essere onesti, date le variazioni nel dialetto SQL, anche il DOP non ti dà il secondo con certezza. Avresti bisogno di un wrapper ORM adeguato per questo.
DSC

le mysql_*funzioni sono una shell nelle funzioni mysqlnd per le nuove versioni di PHP. Quindi, anche se la vecchia libreria client non viene più mantenuta, mysqlnd viene mantenuta :)
hakre

Il problema non è che molti provider di web hosting possono supportare questo stile di design orientato agli oggetti a causa della versione php obsoleta
Raju yourPepe

@RajuGujarati quindi trova un host web che può. In caso contrario, è molto probabile che siano vulnerabili agli attacchi sui loro server.
Alnitak,

106

A proposito di motivi tecnici , ce ne sono solo alcuni, estremamente specifici e usati raramente. Molto probabilmente non li userai mai nella tua vita.
Forse sono troppo ignorante, ma non ho mai avuto l'opportunità di usarli come cose

  • query asincrone non bloccanti
  • stored procedure che restituiscono più set di risultati
  • Crittografia (SSL)
  • Compressione

Se ne hai bisogno, questi sono senza dubbio motivi tecnici per passare dall'estensione mysql a qualcosa di più elegante e moderno.

Tuttavia, ci sono anche alcuni problemi non tecnici che possono rendere la tua esperienza un po 'più difficile

  • l'ulteriore utilizzo di queste funzioni con le moderne versioni di PHP genererà avvisi a livello deprecato. Possono semplicemente essere disattivati.
  • in un futuro lontano, possono eventualmente essere rimossi dalla build PHP predefinita. Non è un grosso problema, poiché mydsql ext verrà spostato in PECL e ogni hoster sarà felice di compilare PHP con esso, poiché non vogliono perdere clienti i cui siti funzionano da decenni.
  • forte resistenza da parte della comunità StackOverflow. "Ogni volta che menzioni queste oneste funzioni, ti viene detto che sono sotto severi tabù.
  • essendo un utente medio di PHP, molto probabilmente la tua idea di utilizzare queste funzioni è soggetta a errori e sbagliata. Solo grazie a tutti questi numerosi tutorial e manuali che ti insegnano nel modo sbagliato. Non le funzioni stesse - devo sottolinearle - ma il modo in cui vengono utilizzate.

Quest'ultimo problema è un problema.
Ma, secondo me, neanche la soluzione proposta è migliore.
Mi sembra un sogno troppo idealistico che tutti quegli utenti PHP imparino a gestire correttamente le query SQL contemporaneamente. Molto probabilmente avrebbero semplicemente cambiato mysql_ * in mysqli_ * meccanicamente, lasciando lo stesso approccio . Soprattutto perché mysqli rende le dichiarazioni preparate incredibilmente dolorose e fastidiose.
Per non parlare del fatto che le istruzioni preparate native non sono sufficienti per proteggere dalle iniezioni di SQL e né mysqli né PDO offrono una soluzione.

Quindi, invece di combattere questa estensione onesta, preferirei combattere pratiche sbagliate ed educare le persone nel modo giusto.

Inoltre, ci sono alcuni motivi falsi o non significativi, come

  • Non supporta le Stored Procedure (che usavamo mysql_query("CALL my_proc");da anni)
  • Non supporta le transazioni (come sopra)
  • Non supporta più dichiarazioni (chi ne ha bisogno?)
  • Non in fase di sviluppo attivo (quindi cosa? Ti influenza in qualche modo pratico?)
  • Manca un'interfaccia OO (per crearne una è questione di diverse ore)
  • Non supporta istruzioni preparate o query con parametri

L'ultimo è un punto interessante. Sebbene mysql ext non supporti le istruzioni preparate native , non sono necessarie per la sicurezza. Possiamo facilmente falsificare le dichiarazioni preparate utilizzando segnaposto gestiti manualmente (proprio come fa DOP):

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

voilà , tutto è parametrizzato e sicuro.

Ma va bene, se non ti piace la scatola rossa nel manuale, sorge un problema di scelta: mysqli o DOP?

Bene, la risposta sarebbe la seguente:

  • Se capisci la necessità di utilizzare un livello di astrazione del database e di cercare un'API per crearne uno, mysqli è un'ottima scelta, in quanto supporta molte funzionalità specifiche di mysql.
  • Se, come la stragrande maggioranza delle persone PHP, stai usando chiamate API non elaborate nel codice dell'applicazione (che è essenzialmente una pratica errata) - DOP è l'unica scelta , poiché questa estensione finge di non essere solo API ma piuttosto un semi-DAL, ancora incompleto ma offre molte funzionalità importanti, con due di esse rende la DOP particolarmente distinta da mysqli:

    • a differenza di mysqli, DOP può associare i segnaposto per valore , il che rende possibili query create dinamicamente senza diverse schermate di codice piuttosto disordinato.
    • a differenza di mysqli, PDO può sempre restituire il risultato della query in un semplice array normale, mentre mysqli può farlo solo su installazioni mysqlnd.

Quindi, se sei un utente medio di PHP e vuoi risparmiare un sacco di mal di testa quando usi dichiarazioni preparate in modo nativo, DOP - di nuovo - è l'unica scelta.
Tuttavia, anche la DOP non è un proiettile d'argento e ha le sue difficoltà.
Quindi, ho scritto soluzioni per tutte le insidie ​​comuni e i casi complessi nel wiki del tag PDO

Tuttavia, a tutti coloro che parlano di estensioni mancano sempre i 2 fatti importanti su Mysqli e DOP:

  1. La dichiarazione preparata non è un proiettile d'argento . Esistono identificatori dinamici che non possono essere associati usando istruzioni preparate. Esistono query dinamiche con un numero sconosciuto di parametri che rendono difficile la creazione di query.

  2. Né mysqli_ * né le funzioni PDO avrebbero dovuto apparire nel codice dell'applicazione.
    Dovrebbe esserci uno strato di astrazione tra loro e il codice dell'applicazione, che farà tutto il lavoro sporco di associazione, loop, gestione degli errori, ecc. All'interno, rendendo il codice dell'applicazione ASCIUTTO e pulito. Soprattutto per i casi complessi come la creazione di query dinamiche.

Quindi, il solo passaggio a DOP o mysqli non è sufficiente. Uno deve usare un ORM, un generatore di query o qualunque classe di astrazione del database invece di chiamare funzioni API non elaborate nel loro codice.
E al contrario, se si dispone di un livello di astrazione tra il codice dell'applicazione e l'API mysql , in realtà non importa quale motore viene utilizzato. È possibile utilizzare mysql ext fino a quando non diventa obsoleto e quindi riscrivere facilmente la classe di astrazione su un altro motore, mantenendo intatto tutto il codice dell'applicazione.

Ecco alcuni esempi basati sulla mia classe safemysql per mostrare come dovrebbe essere una tale classe di astrazione:

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

Confronta questa singola riga con la quantità di codice di cui avrai bisogno con il DOP .
Quindi confronta con una quantità folle di codice di cui avrai bisogno con le dichiarazioni preparate di Mysqli non elaborate. Si noti che la gestione degli errori, la creazione di profili, la registrazione delle query sono già integrate e in esecuzione.

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

Confrontalo con i soliti inserti DOP, quando ogni singolo nome di campo viene ripetuto da sei a dieci volte, in tutti questi numerosi segnaposto, associazioni e definizioni di query denominati.

Un altro esempio:

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

Difficilmente puoi trovare un esempio per il PDO di gestire un caso così pratico.
E sarà troppo prolisso e molto probabilmente non sicuro.

Quindi, ancora una volta - non è solo il driver grezzo a essere la tua preoccupazione, ma la classe di astrazione, utile non solo per gli esempi sciocchi dal manuale per principianti, ma per risolvere qualsiasi problema della vita reale.


20
mysql_*rende le vulnerabilità molto facili da trovare. Poiché PHP è utilizzato da molti utenti inesperti, mysql_*nella pratica è attivamente dannoso, anche se in teoria può essere utilizzato senza intoppi.
Madara's Ghost

4
everything is parameterized and safe- può essere parametrizzato, ma la tua funzione non utilizza istruzioni realmente preparate.
uınbɐɥs

6
Come funziona Not under active developmentsolo lo 0,01% inventato? Se costruisci qualcosa con questa funzione stand-still, aggiorni la tua versione mysql in un anno e finisci con un sistema non funzionante, sono sicuro che ci sono un sacco di persone improvvisamente in quello "0,01%". Direi che deprecatede not under active developmentsono strettamente correlati. Si può dire che non esiste "alcun [degno] motivo" per questo, ma il fatto è che quando viene offerta una scelta tra le opzioni, no active developmentè quasi altrettanto cattivo come deprecateddirei?
Nanne,

1
@MadaraUchiha: puoi spiegare come sono facilmente individuabili le vulnerabilità? Soprattutto nei casi in cui quelle stesse vulnerabilità non influiscono su DOP o MySQLi ... Perché non sono a conoscenza di una sola di cui parli.
Ircmaxell,

4
@ShaquinTrifonoff: certo, non usa dichiarazioni preparate. Ma nemmeno il DOP , che la maggior parte delle persone consiglia su MySQLi. Quindi non sono sicuro che abbia un impatto significativo qui. Il codice sopra (con un po 'più di analisi) è ciò che fa DOP quando si prepara un'istruzione per impostazione predefinita ...
ircmaxell

97

Ci sono molte ragioni, ma forse la più importante è che tali funzioni incoraggiano pratiche di programmazione non sicure perché non supportano dichiarazioni preparate. Dichiarazioni preparate aiutano a prevenire attacchi di iniezione SQL.

Quando si utilizzano le mysql_*funzioni, è necessario ricordare di eseguire i parametri forniti dall'utente mysql_real_escape_string(). Se si dimentica in un solo posto o se si riesce a sfuggire solo a una parte dell'input, il database potrebbe essere soggetto ad attacchi.

L'uso di istruzioni preparate in PDOo mysqlilo renderà più difficile fare questo tipo di errori di programmazione.


3
Purtroppo il supporto scarso in MySQLi_ * per il passaggio di un numero variabile di parametri (come quando si desidera passare un elenco di valori da verificare in una clausola IN) incoraggia il non utilizzo dei parametri, incoraggiando l'uso esattamente delle stesse query concatenate che lasciare vulnerabili le chiamate MySQL_ *.
Calcio d'

5
Ma, ancora una volta, l'insicurezza non è un problema intrinseco delle funzioni mysql_ *, ma un problema di utilizzo errato.
Agamemnus,

2
@Agamemnus Il problema è che mysql_ * semplifica l'implementazione di questo "utilizzo errato", specialmente per programmatori inesperti. Le librerie che implementano istruzioni preparate rendono più difficile fare questo tipo di errore.
Trott,

75

Perché (tra le altre ragioni) è molto più difficile garantire che i dati di input siano disinfettati. Se si utilizzano query parametrizzate, come si fa con DOP o mysqli, è possibile evitare del tutto il rischio.

Ad esempio, qualcuno potrebbe usare "enhzflep); drop table users"come nome utente. Le vecchie funzioni consentiranno di eseguire più istruzioni per query, quindi qualcosa come quel brutto bugger può eliminare un'intera tabella.

Se si dovesse usare DOP di mysqli, il nome utente finirebbe per essere "enhzflep); drop table users".

Vedi bobby-tables.com .


10
The old functions will allow executing of multiple statements per query- no, non lo faranno. Questo tipo di iniezione non è possibile con ext / mysql - l'unico modo in cui questo tipo di iniezione è possibile con PHP e MySQL è quando si utilizza MySQLi e la mysqli_multi_query()funzione. L'iniezione gentile che è possibile con ext / mysql e stringhe senza caratteri di escape è come ' OR '1' = '1estrarre dati dal database che non erano pensati per essere accessibili. In alcune situazioni è possibile iniettare query secondarie, tuttavia non è ancora possibile modificare il database in questo modo.
DaveRandom,

64

Questa risposta è scritta per mostrare quanto sia banale bypassare il codice di convalida dell'utente PHP scritto male, come (e usando cosa) funzionano questi attacchi e come sostituire le vecchie funzioni MySQL con un'istruzione preparata sicura - e fondamentalmente, perché gli utenti StackOverflow (probabilmente con molta reputazione) stanno abbaiando ai nuovi utenti che fanno domande per migliorare il loro codice.

Prima di tutto, non esitare a creare questo database mysql di prova (ho chiamato il mio prep):

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)

Fatto ciò, possiamo passare al nostro codice PHP.

Supponiamo che il seguente script sia il processo di verifica per un amministratore su un sito Web (semplificato ma funzionante se lo si copia e lo si utilizza per i test):

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

Sembra abbastanza legittimo a prima vista.

L'utente deve inserire un login e una password, giusto?

Brillante, non inserire quanto segue:

user: bob
pass: somePass

e inviarlo.

L'output è il seguente:

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

Super! Funzionando come previsto, ora proviamo il nome utente e la password effettivi:

user: Fluffeh
pass: mypass

Sorprendente! Ciao a tutti, il codice ha verificato correttamente un amministratore. È perfetto!

Beh, non proprio. Diciamo che l'utente è una piccola persona intelligente. Diciamo che la persona sono io.

Immettere quanto segue:

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

E l'output è:

The check passed. We have a verified admin!

Complimenti, mi hai appena permesso di entrare nella sezione dei tuoi amministratori super-protetti con me inserendo un nome utente falso e una password falsa. Seriamente, se non mi credi, crea il database con il codice che ho fornito ed esegui questo codice PHP - che a prima vista sembra VERAMENTE verificare il nome utente e la password piuttosto bene.

Quindi, in risposta, QUELLO È IL PERCHÉ STAI UCCELLANDO.

Quindi, diamo un'occhiata a cosa è andato storto e al motivo per cui sono appena entrato nella tua caverna di pipistrelli super amministratore. Ho ipotizzato che non stavi attento con i tuoi input e li ho semplicemente passati direttamente al database. Ho costruito l'input in modo tale da modificare la query che stavi effettivamente eseguendo. Quindi, cosa avrebbe dovuto essere e cosa sarebbe finito?

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

Questa è la query, ma quando sostituiamo le variabili con gli input effettivi che abbiamo usato, otteniamo quanto segue:

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

Vedi come ho costruito la mia "password" in modo che prima chiudesse la singola citazione attorno alla password, quindi introducessi un confronto completamente nuovo? Quindi, solo per sicurezza, ho aggiunto un'altra "stringa" in modo che la singola virgoletta si chiudesse come previsto nel codice che avevamo originariamente.

Tuttavia, non si tratta di persone che ti urlano contro ora, si tratta di mostrarti come rendere il tuo codice più sicuro.

Ok, quindi cosa è andato storto e come possiamo risolverlo?

Questo è un classico attacco di iniezione SQL. Uno dei più semplici per quella materia. Sulla scala dei vettori di attacco, questo è un bambino che attacca un carro armato e vince.

Quindi, come proteggiamo la tua sezione di amministrazione sacra e la rendiamo bella e sicura? La prima cosa da fare sarà smettere di usare quelle mysql_*funzioni davvero vecchie e deprecate . Lo so, hai seguito un tutorial che hai trovato online e funziona, ma è vecchio, è obsoleto e nel giro di pochi minuti, l'ho appena superato senza nemmeno sudare.

Ora hai le migliori opzioni per usare mysqli_ o PDO . Personalmente sono un grande fan di DOP, quindi userò DOP nel resto di questa risposta. Ci sono pro e contro, ma personalmente trovo che i pro superino di gran lunga quelli con. È portatile su più motori di database, che si tratti di MySQL o Oracle o di qualsiasi cosa insanguinata, semplicemente cambiando la stringa di connessione, ha tutte le fantasiose funzionalità che vogliamo usare ed è bello e pulito. Mi piace pulito.

Ora diamo un'occhiata a quel codice, questa volta scritto usando un oggetto 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>

Le principali differenze sono che non ci sono più mysql_*funzioni. Tutto viene eseguito tramite un oggetto PDO, in secondo luogo, utilizza un'istruzione preparata. Ora, qual è una dichiarazione anticipata che chiedi? È un modo per dire al database prima di eseguire una query, qual è la query che stiamo per eseguire. In questo caso, diciamo al database: "Salve, eseguirò un'istruzione select che richiede id, userid e passa dagli utenti della tabella in cui userid è una variabile e anche il passaggio è una variabile".

Quindi, nell'istruzione execute, passiamo al database un array con tutte le variabili che ora si aspetta.

I risultati sono fantastici. Proviamo prima quelle combinazioni di nome utente e password:

user: bob
pass: somePass

L'utente non è stato verificato. Eccezionale.

Che ne dite di:

user: Fluffeh
pass: mypass

Oh, mi sono solo emozionato, ha funzionato: il controllo è passato. Abbiamo un amministratore verificato!

Ora, proviamo i dati che un tipo intelligente dovrebbe inserire per cercare di superare il nostro piccolo sistema di verifica:

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

Questa volta, otteniamo quanto segue:

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

Questo è il motivo per cui ti viene urlato quando pubblichi domande - è perché le persone possono vedere che il tuo codice può essere bypassato anche senza provare. Per favore, usa questa domanda e rispondi per migliorare il tuo codice, per renderlo più sicuro e per usare le funzioni attuali.

Infine, questo non vuol dire che questo è il codice PERFETTO. Ci sono molte altre cose che potresti fare per migliorarlo, ad esempio utilizzare password con hash, assicurarti che quando memorizzi informazioni sensibili nel database, non le memorizzi in testo semplice, hai più livelli di verifica - ma davvero, se cambiate semplicemente il vostro vecchio codice incline all'iniezione in questo, sarete benissimo lungo la strada per scrivere un buon codice - e il fatto che siete arrivati ​​così lontano e state ancora leggendo mi dà la speranza che non implementerete solo questo tipo di codice durante la scrittura di siti Web e applicazioni, ma che potresti uscire e cercare quelle altre cose che ho appena menzionato - e altro ancora. Scrivi il codice migliore che puoi, non il codice più semplice che funziona a malapena.


2
La ringrazio per la risposta! Prendi il mio +1! Vale la pena notare che mysql_*di per sé non è insicuro, ma promuove il codice insicuro tramite tutorial errati e la mancanza di un'istruzione corretta prepara l'API.
Madara's Ghost,

2
password non cancellate, oh l'orrore! = oP Altrimenti +1 per una spiegazione dettagliata.
criptico ツ

33

L'estensione MySQL è la più antica delle tre ed era il modo originale utilizzato dagli sviluppatori per comunicare con MySQL. Questa estensione è ora deprecata a favore delle altre due alternative a causa dei miglioramenti apportati alle versioni più recenti di PHP e MySQL.

  • MySQLi è l'estensione "migliorata" per lavorare con i database MySQL. Sfrutta le funzionalità disponibili nelle versioni più recenti del server MySQL, espone allo sviluppatore un'interfaccia orientata alle funzioni e orientata agli oggetti e fa poche altre cose eleganti.

  • PDO offre un'API che consolida la maggior parte delle funzionalità precedentemente diffuse tra le principali estensioni di accesso al database, ovvero MySQL, PostgreSQL, SQLite, MSSQL, ecc. L'interfaccia espone oggetti di alto livello affinché il programmatore possa lavorare con connessioni al database, query e set di risultati e driver di basso livello eseguono la comunicazione e la gestione delle risorse con il server di database. Molta discussione e lavoro stanno entrando in DOP ed è considerato il metodo appropriato per lavorare con database in codice moderno e professionale.


21

Trovo che le risposte di cui sopra siano davvero lunghe, quindi per riassumere:

L'estensione mysqli ha una serie di vantaggi, i miglioramenti chiave rispetto all'estensione mysql sono:

  • Interfaccia orientata agli oggetti
  • Supporto per dichiarazioni preparate
  • Supporto per più dichiarazioni
  • Supporto per le transazioni
  • Funzionalità di debug avanzate
  • Supporto per server incorporato

Fonte: panoramica di MySQLi


Come spiegato nelle risposte sopra, le alternative a mysql sono mysqli e PDO (PHP Data Objects).

  • L'API supporta le istruzioni preparate sul lato server: supportate da MYSQLi e PDO
  • L'API supporta le istruzioni preparate sul lato client: supportate solo da DOP
  • L'API supporta Stored Procedure: sia MySQLi che PDO
  • L'API supporta più istruzioni e tutte le funzionalità di MySQL 4.1+ - Supportato da MySQLi e principalmente anche da PDO

Sia MySQLi che PDO sono stati introdotti in PHP 5.0, mentre MySQL è stato introdotto prima di PHP 3.0. Un punto da notare è che MySQL è incluso in PHP5.x sebbene deprecato nelle versioni successive.


2
La tua risposta è troppo lunga, mentre il vero riassunto è "mysql ext non c'è più". Questo è tutto
Il tuo senso comune

1
@YourCommonSense La mia risposta è il motivo per cui mysqli ha sostituito mysql. Il punto non è dire che Mysqli esiste oggi, quindi usalo .. Lo sanno tutti!
Ani Menon

1
Bene, a parte il fatto che nessuno ha chiesto perché mysqli abbia sostituito mysql, non risponde neanche a questa domanda. Risponde al motivo per cui mysqli è stato introdotto. Ma non spiega perché a mysql e mysqli non fosse permesso di vivere in parallelo
Your Common Sense

@YourCommonSense Anche la domanda del PO è "Perché dovrei usare qualcos'altro anche se funzionano sul mio sito?" e questo è il motivo per cui ho sottolineato i cambiamenti e i miglioramenti. Potresti guardare tutte le altre risposte che sono lunghe, quindi ho pensato di riassumerlo.
Ani Menon

6

È possibile definire quasi tutte le mysql_*funzioni utilizzando mysqli o DOP. Includili semplicemente sulla tua vecchia applicazione PHP e funzionerà su PHP7. La mia soluzione qui .

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

Invece di mostrare il link per la tua soluzione, aggiungili qui come risposta.
amarnath,

1

Le funzioni simili a questa mysql_connect(), mysql_query()tipo sono le funzioni della versione precedente di PHP ie (PHP 4) e ora non sono in uso.

Questi sono sostituiti da mysqli_connect(), mysqli_query()allo stesso modo nell'ultimo PHP5.

Questo è il motivo dietro l'errore.


2
PHP 5 non è più recente da oltre 2 anni.
Madara's Ghost

1

MySQL deprecato in PHP 5.5.0 e rimosso in PHP 7.0.0. Per un'applicazione grande e vecchia, è difficile cercare e sostituire ogni funzione.

Possiamo usare le funzioni di MySQL creando una funzione wrapper per ogni codice sotto in esecuzione. Clicca qui


-9

Le funzioni mysql_ * sono state deprecate (a partire da PHP 5.5 ) dato che sono state sviluppate funzioni e strutture di codice migliori. Il fatto che la funzione sia stata deprecata significa che non saranno fatti ulteriori sforzi per migliorarla in termini di prestazioni e sicurezza, il che significa che è meno prova per il futuro .

Se hai bisogno di più motivi:

  • Le funzioni mysql_ * non supportano le istruzioni preparate.
  • Le funzioni mysql_ * non supportano l'associazione di parametri.
  • Le funzioni mysql_ * mancano di funzionalità per la programmazione orientata agli oggetti.
  • l'elenco continua ...

18
Questa risposta è obsoleta. Inoltre, non aggiunge nulla di utile alle risposte che già esiste.
Il tuo buon senso,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.