¿Por qué no debería usar las funciones mysql_ * en PHP?

2502

¿Cuáles son las razones técnicas por las cuales uno no debería usar mysql_*funciones? (por ejemplo mysql_query(), mysql_connect()o mysql_real_escape_string())?

¿Por qué debería usar otra cosa incluso si funcionan en mi sitio?

Si no funcionan en mi sitio, ¿por qué recibo errores como

Advertencia: mysql_connect (): No existe tal archivo o directorio

El fantasma de Madara
fuente
Error similar: Error fatal: Error no detectado: Llamada a la función indefinida mysql_connect () ...
Bimal Poudel
21
Desaprobado solo es motivo suficiente para evitarlos
Sasa1234

Respuestas:

2089

La extensión MySQL:

  • No está bajo desarrollo activo
  • Está oficialmente en desuso a partir de PHP 5.5 (lanzado en junio de 2013).
  • Se ha eliminado por completo a partir de PHP 7.0 (lanzado en diciembre de 2015)
    • Esto significa que a partir del 31 de diciembre de 2018 no existe en ninguna versión compatible de PHP. Si está utilizando una versión de PHP que lo admite, está utilizando una versión que no soluciona los problemas de seguridad.
  • Carece de una interfaz OO
  • No es compatible:
    • Consultas asincrónicas sin bloqueo
    • Declaraciones preparadas o consultas parametrizadas
    • Procedimientos almacenados
    • Declaraciones múltiples
    • Actas
    • El "nuevo" método de autenticación de contraseña (activado de forma predeterminada en MySQL 5.6; requerido en 5.7)
    • Cualquiera de las nuevas funcionalidades en MySQL 5.1 o posterior

Como está en desuso, usarlo hace que su código sea menos a prueba de futuro.

La falta de soporte para las declaraciones preparadas es particularmente importante ya que proporcionan un método más claro y menos propenso a errores para escapar y citar datos externos que escapar manualmente con una llamada de función separada.

Vea la comparación de extensiones SQL .

Quentin
fuente
287
Desaprobado solo es razón suficiente para evitarlos. No estarán allí algún día, y no serás feliz si confías en ellos. El resto es solo una lista de cosas que el uso de las extensiones anteriores ha impedido que las personas aprendan.
Tim Post
111
La desaprobación no es la bala mágica que todo el mundo parece pensar que es. PHP mismo no estará allí algún día, sin embargo, confiamos en las herramientas que tenemos a nuestra disposición hoy. Cuando tengamos que cambiar las herramientas, lo haremos.
Carreras de ligereza en órbita
133
@LightnessRacesinOrbit - La desaprobación no es una bala mágica, es una bandera que dice "Reconocemos que esto apesta, así que no lo vamos a apoyar por mucho más tiempo". Si bien tener una mejor prueba de código en el futuro es una buena razón para alejarse de las características obsoletas, no es la única (o incluso la principal). Cambie las herramientas porque hay mejores herramientas, no porque se vea obligado a hacerlo. (Y cambiar las herramientas antes de que te vean obligado a hacerlo significa que no estás aprendiendo las nuevas solo porque tu código ha dejado de funcionar y necesita repararse ayer ... que es el peor momento para aprender nuevas herramientas).
Quentin
18
Una cosa que no he visto mencionada sobre la falta de declaraciones preparadas es el problema de rendimiento. Cada vez que emite una declaración, algo tiene que compilarla para que el demonio MySQL pueda entenderla. Con esta API, si emite 200,000 de la misma consulta en un bucle, eso es 200,000 veces la consulta tiene que ser compilada para que MySQL la entienda. Con las declaraciones preparadas, se compila una vez, y luego los valores se parametrizan en el SQL compilado.
Goldentoa11
20
@symcbean, seguramente no admite declaraciones preparadas. De hecho, esa es la razón principal por la que está en desuso. Sin declaraciones preparadas (fáciles de usar), la extensión mysql a menudo es víctima de ataques de inyección SQL.
rustyx
1287

PHP ofrece tres API diferentes para conectarse a MySQL. Estas son mysql(eliminadas a partir de PHP 7) mysqli, y PDOextensiones.

Las mysql_*funciones solían ser muy populares, pero ya no se recomienda su uso. El equipo de documentación está discutiendo la situación de seguridad de la base de datos, y educar a los usuarios para que se alejen de la extensión ext / mysql comúnmente utilizada es parte de esto (ver php.internals: deprecando ext / mysql ).

Y el equipo de desarrolladores de PHP posterior tomó la decisión de generar E_DEPRECATEDerrores cuando los usuarios se conectan a MySQL, ya sea a través de mysql_connect(), mysql_pconnect()o la funcionalidad de conexión implícita incorporada ext/mysql.

ext/mysqlfue oficialmente obsoleto a partir de PHP 5.5 y se ha eliminado a partir de PHP 7 .

¿Ves la caja roja?

Cuando va a cualquier mysql_*página del manual de funciones, ve un cuadro rojo que explica que ya no debe usarse.

Por qué


Alejarse ext/mysqlno solo se trata de seguridad, sino también de tener acceso a todas las funciones de la base de datos MySQL.

ext/mysqlfue construido para MySQL 3.23 y solo obtuvo muy pocas adiciones desde entonces, manteniendo la compatibilidad con esta versión anterior, lo que hace que el código sea un poco más difícil de mantener. Las características faltantes que no son compatibles con ext/mysqlincluyen: ( del manual de PHP ).

Motivo para no usar la mysql_*función :

  • No bajo desarrollo activo
  • Eliminado a partir de PHP 7
  • Carece de una interfaz OO
  • No admite consultas asincrónicas sin bloqueo
  • No admite declaraciones preparadas o consultas parametrizadas
  • No admite procedimientos almacenados
  • No soporta múltiples declaraciones
  • No admite transacciones
  • No es compatible con toda la funcionalidad en MySQL 5.1

Punto anterior citado de la respuesta de Quentin

La falta de soporte para las declaraciones preparadas es particularmente importante ya que proporcionan un método más claro y menos propenso a errores para escapar y citar datos externos que escapar manualmente con una llamada de función separada.

Ver la comparación de extensiones SQL .


Suprimir advertencias de desaprobación

Mientras el código se convierte a MySQLi/ PDO, los E_DEPRECATEDerrores se pueden suprimir configurando error_reportingen php.ini para excluirE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Tenga en cuenta que esto también ocultará otras advertencias de desaprobación , que, sin embargo, pueden ser para otras cosas que no sean MySQL. ( del manual de PHP )

El artículo PDO vs. MySQLi: ¿Cuál debería usar? por Dejan Marjanovic te ayudará a elegir.

Y una mejor manera es PDO, y ahora estoy escribiendo un PDOtutorial simple .


Un tutorial PDO simple y breve


P. La primera pregunta en mi mente fue: ¿qué es 'PDO'?

A. " PDO - PHP Data Objects - es una capa de acceso a la base de datos que proporciona un método uniforme de acceso a múltiples bases de datos".

texto alternativo


Conectando a MySQL

Con mysql_*función o podemos decirlo a la antigua usanza (en desuso en PHP 5.5 y superior)

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

Con PDO: Todo lo que necesitas hacer es crear un nuevo PDOobjeto. El constructor acepta parámetros para especificar la fuente de base de datos PDO'constructor s mayormente toma cuatro parámetros que son DSN(nombre de la fuente de datos) y, opcionalmente username, password.

Aquí creo que estás familiarizado con todos excepto DSN; esto es nuevo en PDO. B DSNes básicamente una serie de opciones que indican PDOqué controlador usar y los detalles de conexión. Para mayor referencia, consulte PDO MySQL DSN .

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

Nota: también puede usar charset=UTF-8, pero a veces causa un error, por lo que es mejor usarlo utf8.

Si hay algún error de conexión, arrojará un PDOExceptionobjeto que puede ser atrapado para manejarlo Exceptionmás.

Buena lectura : Conexiones y gestión de conexiones ¶

También puede pasar varias opciones de controlador como una matriz al cuarto parámetro. Recomiendo pasar el parámetro que pone PDOen modo de excepción. Debido a que algunos PDOcontroladores no admiten declaraciones preparadas nativas, por lo tanto, PDOrealiza la emulación de la preparación. También le permite habilitar manualmente esta emulación. Para usar las declaraciones preparadas nativas del lado del servidor, debe configurarlas explícitamente false.

El otro es desactivar la emulación de preparación que está habilitada en el MySQLcontrolador de forma predeterminada, pero la emulación de preparación debe estar desactivada para usar de PDOmanera segura.

Más adelante explicaré por qué se debe desactivar la emulación de preparación. Para encontrar la razón, consulte esta publicación .

Solo se puede usar si está utilizando una versión anterior MySQLque no recomiendo.

A continuación se muestra un ejemplo de cómo puede hacerlo:

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

¿Podemos establecer atributos después de la construcción de PDO?

, también podemos establecer algunos atributos después de la construcción PDO con el setAttributemétodo:

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

Manejo de errores


El manejo de errores es mucho más fácil PDOque mysql_*.

Una práctica común cuando se usa mysql_*es:

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

OR die()no es una buena forma de manejar el error, ya que no podemos manejarlo die. Simplemente terminará el script abruptamente y luego hará eco del error en la pantalla que generalmente NO desea mostrar a sus usuarios finales, y permitirá que los hackers sangrientos descubran su esquema. Alternativamente, los valores de retorno de las mysql_*funciones a menudo se pueden usar junto con mysql_error () para manejar errores.

PDOofrece una mejor solución: excepciones. Cualquier cosa que hacemos con PDOdebe ser envuelto en una try- catchbloque. Podemos forzar PDOuno de los tres modos de error configurando el atributo del modo de error. Hay tres modos de manejo de errores a continuación.

  • PDO::ERRMODE_SILENT. Solo establece códigos de error y actúa casi de la misma manera mysql_*en que debe verificar cada resultado y luego mirar $db->errorInfo();para obtener los detalles del error.
  • PDO::ERRMODE_WARNINGAumento E_WARNING. (Advertencias en tiempo de ejecución (errores no fatales). La ejecución del script no se detiene).
  • PDO::ERRMODE_EXCEPTION: Lanzar excepciones. Representa un error provocado por PDO. No debe lanzar un PDOExceptiondesde su propio código. Consulte Excepciones para obtener más información sobre excepciones en PHP. Actúa muy parecido a or die(mysql_error());cuando no es atrapado. Pero a diferencia de esto or die(), PDOExceptionse puede atrapar y manejar con gracia si elige hacerlo.

Buena lectura :

Me gusta:

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

Y se puede envolver en try- catch, como a continuación:

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

Usted no tiene que manejar con try- catchen este momento. Puede atraparlo en cualquier momento apropiado, pero le recomiendo que use try- catch. También puede tener más sentido detectarlo fuera de la función que llama al PDOmaterial:

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

Además, puede manejarlo or die()o podemos decir que me gusta mysql_*, pero será muy variado. Puede ocultar los mensajes de error peligrosos en producción girando display_errors offy simplemente leyendo su registro de errores.

Ahora, después de leer todas las cosas de arriba, es probable que esté pensando: ¿qué diablos es que cuando sólo quiero empezar a inclinarse simples SELECT, INSERT, UPDATE, o DELETEdeclaraciones? No te preocupes, aquí vamos:


Seleccionar datos

PDO seleccionar imagen

Entonces, lo que estás haciendo mysql_*es:

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

Ahora adentro PDO, puedes hacer esto como:

<?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 : Si está utilizando el método que se muestra a continuación ( query()), este método devuelve un PDOStatementobjeto. Entonces, si desea obtener el resultado, úselo como se indicó anteriormente.

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

En PDO Data, se obtiene a través del ->fetch()método de su manejador de estado de cuenta. Antes de llamar a fetch, el mejor enfoque sería decirle a PDO cómo desea que se recuperen los datos. En la sección a continuación estoy explicando esto.

Modos de recuperación

Tenga en cuenta el uso de PDO::FETCH_ASSOCen el código fetch()y fetchAll()arriba. Esto le indica PDOque devuelva las filas como una matriz asociativa con los nombres de campo como claves. También hay muchos otros modos de recuperación que explicaré uno por uno.

En primer lugar, explico cómo seleccionar el modo de búsqueda:

 $stmt->fetch(PDO::FETCH_ASSOC)

En lo anterior, he estado usando fetch(). También puedes usar:

Ahora vengo a buscar el modo:

  • PDO::FETCH_ASSOC: devuelve una matriz indexada por nombre de columna como se devolvió en su conjunto de resultados
  • PDO::FETCH_BOTH (predeterminado): devuelve una matriz indexada tanto por el nombre de la columna como por el número de la columna indexada en 0 como se devolvió en su conjunto de resultados

¡Incluso hay más opciones! Lea sobre todos ellos en la PDOStatementdocumentación de Fetch. .

Obteniendo el recuento de filas :

En lugar de usar mysql_num_rowspara obtener el número de filas devueltas, puede obtener ay PDOStatementhacer rowCount(), como:

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

Obteniendo la última identificación insertada

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

Insertar y actualizar o eliminar declaraciones

Insertar y actualizar imagen PDO

Lo que estamos haciendo en mysql_*función es:

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

Y en pdo, esto mismo se puede hacer:

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

En la consulta anterior, PDO::execejecute una instrucción SQL y devuelva el número de filas afectadas.

Insertar y eliminar se cubrirá más adelante.

El método anterior solo es útil cuando no está utilizando variable en la consulta. Pero cuando necesite usar una variable en una consulta, nunca intente como lo anterior y allí para la declaración preparada o la declaración parametrizada .


Declaraciones preparadas

P. ¿Qué es una declaración preparada y por qué los necesito?
R. Una declaración preparada es una declaración SQL precompilada que se puede ejecutar varias veces enviando solo los datos al servidor.

El flujo de trabajo típico de usar una declaración preparada es el siguiente ( citado en Wikipedia tres puntos 3 ):

  1. Preparar : la aplicación crea la plantilla de extracto y la envía al sistema de gestión de bases de datos (DBMS). Ciertos valores se dejan sin especificar, llamados parámetros, marcadores de posición o variables de enlace (etiquetadas a ?continuación):

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

  2. El DBMS analiza, compila y realiza la optimización de consultas en la plantilla de declaración, y almacena el resultado sin ejecutarlo.

  3. Ejecutar : en un momento posterior, la aplicación proporciona (o enlaza) valores para los parámetros, y el DBMS ejecuta la instrucción (posiblemente devuelve un resultado). La aplicación puede ejecutar la declaración tantas veces como quiera con diferentes valores. En este ejemplo, podría suministrar 'Pan' para el primer parámetro y 1.00para el segundo parámetro.

Puede usar una declaración preparada incluyendo marcadores de posición en su SQL. Básicamente, hay tres sin marcadores de posición (no intente esto con la variable por encima de uno), uno con marcadores de posición sin nombre y uno con marcadores de posición con nombre.

P. Entonces, ¿qué se denominan marcadores de posición y cómo los uso?
A. Marcadores de posición con nombre. Use nombres descriptivos precedidos por dos puntos, en lugar de signos de interrogación. No nos importa la posición / orden de valor en el marcador de posición de nombre:

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

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

También puedes enlazar usando una matriz de ejecución:

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

Otra buena característica para los OOPamigos es que los marcadores de posición con nombre tienen la capacidad de insertar objetos directamente en su base de datos, suponiendo que las propiedades coincidan con los campos con nombre. Por ejemplo:

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

P. Entonces, ¿qué son marcadores de posición sin nombre y cómo los uso?
A. Tengamos un ejemplo:

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

y

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

En lo anterior, puede ver esos en ?lugar de un nombre como en un marcador de posición de nombre. Ahora, en el primer ejemplo, asignamos variables a los distintos marcadores de posición ( $stmt->bindValue(1, $name, PDO::PARAM_STR);). Luego, asignamos valores a esos marcadores de posición y ejecutamos la declaración. En el segundo ejemplo, el primer elemento de matriz va al primero ?y el segundo al segundo ?.

NOTA : En los marcadores de posición sin nombre , debemos cuidar el orden correcto de los elementos en la matriz que estamos pasando al PDOStatement::execute()método.


SELECT, INSERT, UPDATE, DELETEPreparado consultas

  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:

Sin embargo PDOy / o MySQLino son completamente seguros. Verifique la respuesta ¿Son suficientes las declaraciones preparadas por PDO para evitar la inyección de SQL? por ircmaxell . Además, estoy citando alguna parte de su respuesta:

$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 /*"));
NullPoiиteя
fuente
15
Lo que la buena lectura anterior debería mencionar de manera propagable: la declaración preparada elimina cualquier uso significativo de IN (...) construct.
Eugen Rieck
24
La pregunta era "¿Por qué no debería usar las funciones mysql_ * en PHP"? Esta respuesta, si bien es impresionante y está llena de información útil, queda MUY fuera de alcance y, como dice @trejder, 8 de cada 10 personas se perderán esa información simplemente porque no tienen 4 horas para tratar de trabajar. eso. Esto sería mucho más valioso desglosado y utilizado como respuesta a varias preguntas más precisas.
Alex McMillan
Persoanlly prefiero mysqli y PDO. Pero para el manejo de troqueles, probé una alternativa de excepción. function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();Funciona para lanzar excepciones.
kuldeep.kamboj
usted enumera Doesn't support non-blocking, asynchronous queriescomo una razón para no usar mysql_; también debe enumerar eso como una razón para no usar PDO, porque PDO tampoco lo admite. (pero MySQLi lo admite)
hanshenrik
¿es posible usar Charset utf8mb4_unicode_ci ya que tengo una base de datos que está usando esto?
Ryan Stone
301

Primero, comencemos con el comentario estándar que les damos a todos:

Por favor, no use mysql_*funciones en código nuevo . Ya no se mantienen y están oficialmente en desuso . ¿Ves el cuadro rojo ? Aprenda sobre las declaraciones preparadas y use PDO o MySQLi ; este artículo le ayudará a decidir cuál. Si elige PDO, aquí hay un buen tutorial .

Veamos esto, oración por oración, y expliquemos:

  • Ya no se mantienen y están oficialmente en desuso

    Esto significa que la comunidad PHP está abandonando gradualmente el soporte para estas funciones muy antiguas. ¡Es probable que no existan en una versión futura (reciente) de PHP! El uso continuo de estas funciones puede romper su código en un futuro (no tan lejano).

    ¡NUEVO! - ext / mysql ahora está oficialmente en desuso desde PHP 5.5!

    ¡Más nuevo! ext / mysql ha sido eliminado en PHP 7 .

  • En cambio, debe aprender de las declaraciones preparadas

    mysql_*La extensión no admite declaraciones preparadas , que es (entre otras cosas) una contramedida muy efectiva contra la inyección SQL . Se corrigió una vulnerabilidad muy grave en las aplicaciones dependientes de MySQL que permite a los atacantes obtener acceso a su script y realizar cualquier consulta posible en su base de datos.

    Para obtener más información, consulte ¿Cómo puedo evitar la inyección de SQL en PHP?

  • ¿Ves la caja roja?

    Cuando vaya a cualquier mysqlpágina del manual de funciones, verá un cuadro rojo que explica que ya no se debe usar.

  • Use PDO o MySQLi

    Hay alternativas mejores, más robustas y bien construidas, PDO - PHP Database Object , que ofrece un enfoque completo de OOP para la interacción de la base de datos, y MySQLi , que es una mejora específica de MySQL.

El fantasma de Madara
fuente
66
Hay una cosa más: creo que la función todavía existe en PHP por una sola razón: compatibilidad con sistemas de CMS, comercio electrónico, tablones de anuncios antiguos, obsoletos pero que todavía ejecutan etc. Finalmente, se eliminará y tendrá que volver a escribir su aplicación ...
Kamil
44
@Kamil: Eso es cierto, pero no es realmente una razón por la que no deberías usarlo. La razón para no usarlo es porque es antiguo, inseguro, etc. :)
El Fantasma de Madara
44
@ Mario: los desarrolladores de PHP tienen un proceso, y acaban de votar a favor de desaprobar formalmente ext / mysql a partir del 5.5. Ya no es un problema hipotético.
COSUDE
2
Agregar un par de líneas adicionales con una técnica probada como PDO o MySQLi todavía ofrece la facilidad de uso que PHP siempre ha ofrecido. Espero, por el bien del desarrollador, él / ella sepa que ver estas funciones mysql_ * horribles en cualquier tutorial en realidad resta valor a la lección, y debería decirle al OP que este tipo de código es muuuy hace 10 años, y debería cuestionar el relevancia del tutorial, también!
FredTheWebGuy
1
Lo que la respuesta debería mencionar: la declaración preparada elimina cualquier uso significativo de IN (...) construct.
Eugen Rieck
217

Facilidad de uso

Ya se mencionaron las razones analíticas y sintéticas. Para los recién llegados hay un incentivo más significativo para dejar de usar las funciones mysql_ anticuadas.

Las API de bases de datos contemporáneas son más fáciles de usar.

Son principalmente los parámetros enlazados los que pueden simplificar el código. Y con excelentes tutoriales (como se ve arriba) la transición a PDO no es demasiado ardua.

Reescribir una base de código más grande a la vez, sin embargo, lleva tiempo. Raison d'être para esta alternativa intermedia:

Funciones pdo_ * equivalentes en lugar de mysql_ *

Usando < pdo_mysql.php > puede cambiar de las antiguas funciones mysql_ con un mínimo esfuerzo . Agrega pdo_envoltorios de funciones que reemplazan a sus mysql_contrapartes.

  1. Simplemente en cada script de invocación que tiene que interactuar con la base de datos. include_once("pdo_mysql.php");

  2. Elimine el mysql_prefijo de la función en todas partes y reemplácelo con pdo_.

    • mysql_connect() se convierte pdo_connect()
    • mysql_query() se convierte pdo_query()
    • mysql_num_rows() se convierte pdo_num_rows()
    • mysql_insert_id() se convierte pdo_insert_id()
    • mysql_fetch_array() se convierte pdo_fetch_array()
    • mysql_fetch_assoc() se convierte pdo_fetch_assoc()
    • mysql_real_escape_string() se convierte pdo_real_escape_string()
    • y así...

  3. Su código funcionará igual y aún se verá igual:

    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à.
Su código está usando PDO.
Ahora es el momento de utilizarlo realmente .

Los parámetros enlazados pueden ser fáciles de usar.

Solo necesita una API menos difícil de manejar.

pdo_query()agrega soporte muy fácil para los parámetros enlazados. Convertir el código antiguo es sencillo:

Mueva sus variables fuera de la cadena SQL.

  • Añádalos como parámetros de función delimitados por comas a pdo_query().
  • Coloque signos de interrogación ?como marcadores de posición donde estaban las variables antes.
  • Deshágase de las 'comillas simples que anteriormente incluían valores / variables de cadena.

La ventaja se vuelve más obvia para un código más largo.

A menudo, las variables de cadena no solo se interpolan en SQL, sino que se concatenan con llamadas de escape intermedias.

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 los ?marcadores de posición aplicados, no tiene que molestarse con eso:

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

Recuerde que pdo_ * todavía permite o .
Simplemente no escape una variable y la asocie en la misma consulta.

  • La función de marcador de posición es proporcionada por el PDO real detrás de él.
  • Por lo tanto, también permitió :namedlistas de marcadores de posición más adelante.

Más importante aún, puede pasar $ _REQUEST [] variables de forma segura detrás de cualquier consulta. Cuando los <form>campos enviados coinciden exactamente con la estructura de la base de datos, es aún más corta:

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

Tanta simplicidad. Pero volvamos a algunos consejos de reescritura más y razones técnicas sobre por qué es posible que desee deshacerse mysql_y escapar.

Repara o elimina cualquier sanitize()función de la vieja escuela

Una vez que haya convertido todas las mysql_llamadas pdo_querycon parámetros vinculados, elimine todas las pdo_real_escape_stringllamadas redundantes .

En particular, se debe corregir cualquier sanitizeo cleano filterThiso clean_datafunciones como se anuncia por tutoriales de fecha, de una forma u otra:

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

El error más evidente aquí es la falta de documentación. Más significativamente, el orden de filtrado estaba exactamente en el orden incorrecto.

  • El orden correcto habría sido: obsoleto stripslashescomo la llamada más interna, luego trim, después strip_tags, htmlentitiespara el contexto de salida, y solo por último, _escape_stringya que su aplicación debería preceder directamente a la interacción SQL.

  • Pero como primer paso, simplemente deshágase de la_real_escape_string llamada.

  • Es posible que deba mantener el resto de su sanitize()función por ahora si su base de datos y el flujo de la aplicación esperan cadenas seguras para el contexto HTML. Agregue un comentario que aplique solo el escape de HTML en adelante.

  • El manejo de cadenas / valores se delega a PDO y sus declaraciones parametrizadas.

  • Si hubo alguna mención stripslashes()en su función de desinfección, puede indicar una supervisión de nivel superior.

    • Eso estaba comúnmente allí para deshacer el daño (doble escape) de los obsoletos magic_quotes. Lo que, sin embargo, se soluciona mejor centralmente , no cadena por cadena.

    • Utilice uno de los enfoques de reversión del usuario . Luego elimine el stripslashes()en la sanitizefunción.

    Nota histórica sobre magic_quotes. Esa característica está en desuso correctamente. Sin embargo , a menudo se retrata incorrectamente como una función de seguridad fallida . Pero magic_quotes es una característica de seguridad tan fallida como las pelotas de tenis han fallado como fuente de nutrición. Ese simplemente no era su propósito.

    La implementación original en PHP2 / FI lo introdujo explícitamente con solo "las comillas se escaparán automáticamente para que sea más fácil pasar los datos del formulario directamente a las consultas msql ". En particular, era accidentalmente seguro usarlo con mSQL , ya que solo admitía ASCII.
    Luego PHP3 / Zend reintrodujo magic_quotes para MySQL y lo documentaron mal. Pero originalmente era solo una característica de conveniencia , no pensada para la seguridad.

Cómo difieren las declaraciones preparadas

Cuando codifica las variables de cadena en las consultas SQL, no solo se vuelve más intrincado para que lo siga. También es un esfuerzo extraño para MySQL segregar el código y los datos nuevamente.

Las inyecciones de SQL simplemente son cuando los datos sangran en el contexto del código . Un servidor de base de datos no puede detectar posteriormente dónde PHP originalmente pegó variables entre cláusulas de consulta.

Con los parámetros enlazados, separa el código SQL y los valores de contexto SQL en su código PHP. Pero no se vuelve a mezclar detrás de escena (excepto con PDO :: EMULATE_PREPARES). Su base de datos recibe los comandos SQL no variados y los valores variables 1: 1.

Si bien esta respuesta subraya que debe preocuparse por las ventajas de legibilidad de la caída mysql_. De vez en cuando también hay una ventaja de rendimiento (INSERT repetidos con solo valores diferentes) debido a esta separación visible y técnica de datos / código.

Tenga en cuenta que el enlace de parámetros aún no es una solución única mágica contra todas las inyecciones SQL. Maneja el uso más común para datos / valores. Pero no se pueden incluir en la lista blanca los identificadores de tabla / nombre de columna, ayudar con la construcción de cláusulas dinámicas o simplemente listas de valores de matriz simple.

Uso de PDO híbrido

Estas pdo_*funciones de contenedor hacen una API de stop-gap amigable con la codificación. (Es más o menos lo que MYSQLIpodría haber sido si no fuera por el cambio de firma de la función idiosincrásica). También exponen la verdadera DOP en la mayoría de los casos.
La reescritura no tiene por qué detenerse en el uso de los nuevos nombres de funciones pdo_. Podría uno por uno hacer la transición de cada pdo_query () en una simple $ pdo-> prepare () -> execute () call.

Sin embargo, es mejor comenzar a simplificar nuevamente. Por ejemplo, la obtención de resultados comunes:

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

Se puede reemplazar con solo una iteración foreach:

foreach ($result as $row) {

O mejor aún, una recuperación directa y completa de la matriz:

$result->fetchAll();

Obtendrá advertencias más útiles en la mayoría de los casos que PDO o mysql_ generalmente proporcionan después de consultas fallidas.

Otras opciones

Así que con suerte esto visualizó algunas razones prácticas y un camino digno de abandonar mysql_.

Simplemente cambiando a no es suficiente. pdo_query()También es solo una interfaz en él.

A menos que también introduzca el enlace de parámetros o pueda utilizar algo más de la API más agradable, es un cambio sin sentido. Espero que sea retratado lo suficientemente simple como para no alentar el desánimo a los recién llegados. (La educación generalmente funciona mejor que la prohibición).

Si bien califica para la categoría de lo más simple que podría funcionar, también sigue siendo un código muy experimental. Lo acabo de escribir el fin de semana. Sin embargo, hay una gran cantidad de alternativas. Simplemente busque en Google la abstracción de la base de datos PHP y explore un poco. Siempre ha habido y habrá muchas bibliotecas excelentes para tales tareas.

Si desea simplificar aún más la interacción de su base de datos, vale la pena intentarlo con mapeadores como Paris / Idiorm . Al igual que ya nadie usa el DOM blando en JavaScript, hoy en día no tiene que cuidar una interfaz de base de datos sin formato.

mario
fuente
8
Tenga cuidado con la pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);función - es decir:pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');
rickyduck
@Tom Claro, aunque no se mantuvo mucho (0.9.2 fue el último), puede crear una cuenta fósil , agregar a la wiki o presentar un informe de error (sin registro IIRC).
mario
pdo_real_escape_string() <- ¿Es incluso una función real, no puedo encontrar ninguna documentación para ello? Por favor, publique una fuente para esto.
Ryan Stone
144

Las mysql_funciones:

  1. están desactualizadas, ya no se mantienen
  2. no le permite moverse fácilmente a otro backend de base de datos
  3. no admite declaraciones preparadas, por lo tanto
  4. Animar a los programadores a utilizar la concatenación para crear consultas, lo que genera vulnerabilidades de inyección SQL.
Alnitak
fuente
18
# 2 es igualmente ciertomysqli_
eggyal
16
Para ser justos, dadas las variaciones en el dialecto SQL, incluso PDO no le da el # 2 con ningún grado de certeza. Necesitaría un contenedor ORM adecuado para eso.
SDC
la mysql_*función es un shell en las funciones de mysqlnd para las nuevas versiones de PHP. Así que incluso si la biblioteca cliente de edad no se mantiene más, mysqlnd se mantiene :)
hakre
El problema no es que muchos proveedores de alojamiento web puedan soportar ese estilo de diseño orientado a objetos debido a la versión obsoleta de php
Raju yourPepe
@RajuGujarati, así que encuentra un proveedor de alojamiento web que pueda. Si su proveedor de alojamiento web no lo hace, hay muchas posibilidades de que sean vulnerables a los ataques en sus servidores.
Alnitak
106

Hablando de razones técnicas , solo hay unas pocas, extremadamente específicas y raramente utilizadas. Lo más probable es que nunca los uses en tu vida.
Tal vez soy demasiado ignorante, pero nunca tuve la oportunidad de usar cosas como

  • consultas asincrónicas sin bloqueo
  • procedimientos almacenados que devuelven múltiples conjuntos de resultados
  • Cifrado (SSL)
  • Compresión

Si los necesita, estas son sin duda razones técnicas para alejarse de la extensión mysql hacia algo más elegante y moderno.

Sin embargo, también hay algunos problemas no técnicos, que pueden hacer que su experiencia sea un poco más difícil

  • El uso posterior de estas funciones con versiones modernas de PHP generará avisos de nivel obsoleto. Simplemente se pueden apagar.
  • en un futuro lejano, posiblemente puedan eliminarse de la compilación predeterminada de PHP. No es un gran problema, ya que mydsql ext se trasladará a PECL y todos los proveedores de alojamiento estarán encantados de compilar PHP con él, ya que no quieren perder clientes cuyos sitios estuvieron funcionando durante décadas.
  • fuerte resistencia de la comunidad de Stackoverflow. Cada vez que mencionas estas funciones honestas, te dicen que están bajo un estricto tabú.
  • Siendo un usuario promedio de PHP, lo más probable es que su idea de usar estas funciones sea propensa a errores y esté equivocada. Solo por todos estos numerosos tutoriales y manuales que te enseñan el camino equivocado. No las funciones en sí mismas, tengo que enfatizarlo, sino la forma en que se usan.

Este último problema es un problema.
Pero, en mi opinión, la solución propuesta tampoco es mejor.
Me parece un sueño demasiado idealista que todos esos usuarios de PHP aprenderán a manejar las consultas SQL correctamente de una vez. Lo más probable es que simplemente cambien mysql_ * a mysqli_ * mecánicamente, dejando el enfoque igual . Especialmente porque mysqli hace que el uso de declaraciones preparadas sea increíblemente doloroso y problemático.
Sin mencionar que las declaraciones preparadas nativas no son suficientes para proteger de las inyecciones de SQL, y ni mysqli ni PDO ofrecen una solución.

Entonces, en lugar de luchar contra esta extensión honesta, preferiría luchar contra las prácticas incorrectas y educar a las personas de la manera correcta.

Además, hay algunas razones falsas o no significativas, como

  • No es compatible con los procedimientos almacenados (lo estábamos usando mysql_query("CALL my_proc");durante años)
  • No admite transacciones (igual que arriba)
  • No admite declaraciones múltiples (¿quién las necesita?)
  • No bajo desarrollo activo (¿y qué? ¿ Te afecta de manera práctica?)
  • Carece de una interfaz OO (crear una es cuestión de varias horas)
  • No es compatible con declaraciones preparadas o consultas parametrizadas

El último es un punto interesante. Aunque mysql ext no admite declaraciones preparadas nativas , no son necesarias para la seguridad. Podemos falsificar fácilmente declaraciones preparadas utilizando marcadores de posición manejados manualmente (al igual que 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);

voila , todo está parametrizado y seguro.

Pero está bien, si no le gusta el cuadro rojo en el manual, surge un problema de elección: mysqli o PDO?

Bueno, la respuesta sería la siguiente:

  • Si comprende la necesidad de usar una capa de abstracción de base de datos y busca una API para crear una, mysqli es una muy buena opción, ya que de hecho es compatible con muchas características específicas de mysql.
  • Si, como la gran mayoría de la gente de PHP, está utilizando llamadas API sin procesar directamente en el código de la aplicación (lo que es esencialmente una práctica incorrecta), PDO es la única opción , ya que esta extensión pretende ser no solo API sino más bien un semi-DAL, Todavía está incompleto pero ofrece muchas características importantes, con dos de ellas hace que PDO se distinga críticamente de mysqli:

    • A diferencia de mysqli, PDO puede enlazar marcadores de posición por valor , lo que hace factibles las consultas creadas dinámicamente sin varias pantallas de código bastante desordenado.
    • a diferencia de mysqli, PDO siempre puede devolver el resultado de la consulta en una matriz simple, mientras que mysqli solo puede hacerlo en instalaciones mysqlnd.

Entonces, si usted es un usuario promedio de PHP y desea ahorrarse un montón de dolores de cabeza al usar declaraciones preparadas nativas, PDO, nuevamente, es la única opción.
Sin embargo, PDO no es una bala de plata también y tiene sus dificultades.
Entonces, escribí soluciones para todos los escollos comunes y casos complejos en el wiki de la etiqueta PDO

Sin embargo, todos los que hablan de extensiones siempre pierden los 2 hechos importantes sobre Mysqli y PDO:

  1. La declaración preparada no es una bala de plata . Hay identificadores dinámicos que no se pueden vincular mediante declaraciones preparadas. Hay consultas dinámicas con un número desconocido de parámetros que hace que la creación de consultas sea una tarea difícil.

  2. Ni mysqli_ * ni las funciones PDO deberían haber aparecido en el código de la aplicación.
    Debería haber una capa de abstracción entre ellos y el código de la aplicación, que hará todo el trabajo sucio de encuadernación, bucle, manejo de errores, etc. dentro, haciendo que el código de la aplicación esté SECO y limpio. Especialmente para los casos complejos como la construcción dinámica de consultas.

Entonces, solo cambiar a PDO o mysqli no es suficiente. Uno tiene que usar un ORM, o un generador de consultas, o cualquier clase de abstracción de base de datos en lugar de llamar a las funciones API sin procesar en su código.
Y, por el contrario, si tiene una capa de abstracción entre el código de su aplicación y la API mysql, en realidad no importa qué motor se use. Puede usar mysql ext hasta que quede obsoleto y luego reescribir fácilmente su clase de abstracción a otro motor, con todo el código de la aplicación intacto.

Aquí hay algunos ejemplos basados ​​en mi clase safemysql para mostrar cómo debería ser una clase de abstracción como esta:

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

Compare esta única línea con la cantidad de código que necesitará con PDO .
Luego compare con la cantidad loca de código que necesitará con las declaraciones preparadas de Mysqli sin procesar. Tenga en cuenta que el manejo de errores, la creación de perfiles y el registro de consultas ya están integrados y en ejecución.

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

Compárelo con las inserciones PDO habituales, cuando cada nombre de campo se repite de seis a diez veces, en todos estos numerosos marcadores de posición con nombre, enlaces y definiciones de consulta.

Otro ejemplo:

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

Difícilmente puede encontrar un ejemplo para que PDO maneje un caso tan práctico.
Y será demasiado prolijo y probablemente inseguro.

Entonces, una vez más, no es solo el controlador sin formato lo que debe preocupar, sino la clase de abstracción, útil no solo para ejemplos tontos del manual para principiantes, sino para resolver cualquier problema de la vida real.

Su sentido común
fuente
20
mysql_*hace que las vulnerabilidades sean muy fáciles de encontrar. Dado que PHP es utilizado por una gran cantidad de usuarios novatos, mysql_*es activamente dañino en la práctica, incluso si en teoría se puede usar sin problemas.
El fantasma de Madara el
44
everything is parameterized and safe- puede estar parametrizado, pero su función no utiliza declaraciones preparadas reales .
El
66
¿Cómo es Not under active developmentsolo para ese '0.01%' inventado? Si crea algo con esta función de parada, actualice su versión de mysql en un año y termine con un sistema que no funciona, estoy seguro de que de repente hay mucha gente en ese '0.01%'. Yo diría eso deprecatedy not under active developmentestán estrechamente relacionados. Puede decir que "no hay una razón [digna]" para ello, pero el hecho es que cuando se le ofrece una opción entre las opciones, ¿ no active developmentes casi tan malo como deprecatedyo diría?
Nanne
1
@MadaraUchiha: ¿Puede explicar cómo las vulnerabilidades son muy fáciles de encontrar? Especialmente en los casos en que esas mismas vulnerabilidades no afectan a PDO o MySQLi ... Porque no conozco una sola de la que hables.
ircmaxell
44
@ShaquinTrifonoff: claro, no usa declaraciones preparadas. Pero tampoco lo hace PDO , que la mayoría de la gente recomienda sobre MySQLi. Así que no estoy seguro de que tenga un impacto significativo aquí. El código anterior (con un poco más de análisis) es lo que hace PDO cuando prepara una declaración por defecto ...
ircmaxell
97

Hay muchas razones, pero quizás la más importante es que esas funciones fomentan prácticas de programación inseguras porque no admiten declaraciones preparadas. Las declaraciones preparadas ayudan a prevenir ataques de inyección SQL.

Al usar mysql_*funciones, debe recordar ejecutar los parámetros proporcionados por el usuario mysql_real_escape_string(). Si olvida en un solo lugar o si escapa solo una parte de la entrada, su base de datos puede estar sujeta a ataques.

El uso de declaraciones preparadas en PDOo mysqlihará que este tipo de errores de programación sea más difícil de hacer.

Trott
fuente
3
Desafortunadamente, el pobre soporte en MySQLi_ * para pasar un número variable de parámetros (como cuando desea pasar una lista de valores para verificar en una cláusula IN) alienta el no uso de parámetros, alentando el uso de exactamente las mismas consultas concatenadas que deja las llamadas MySQL_ * vulnerables.
Kickstart
55
Pero, una vez más, la inseguridad no es un problema inherente de las funciones mysql_ *, sino un problema de uso incorrecto.
Agamemnus
2
@Agamemnus El problema es que mysql_ * facilita la implementación de ese "uso incorrecto", especialmente para programadores inexpertos. Las bibliotecas que implementan declaraciones preparadas hacen que sea más difícil cometer ese tipo de error.
Trott
75

Porque (entre otras razones) es mucho más difícil garantizar que los datos de entrada estén desinfectados. Si usa consultas parametrizadas, como lo hace con PDO o mysqli, puede evitar por completo el riesgo.

Como ejemplo, alguien podría usarlo "enhzflep); drop table users"como nombre de usuario. Las funciones antiguas permitirán ejecutar múltiples declaraciones por consulta, por lo que algo como ese desagradable bugger puede eliminar una tabla completa.

Si uno usara PDO de mysqli, el nombre de usuario terminaría siendo "enhzflep); drop table users".

Ver bobby-tables.com .

enhzflep
fuente
10
The old functions will allow executing of multiple statements per queryNo, no lo harán. Ese tipo de inyección no es posible con ext / mysql: la única forma en que este tipo de inyección es posible con PHP y MySQL es cuando se usa MySQLi y la mysqli_multi_query()función. El tipo de inyección que es posible con ext / mysql y cadenas sin escape es como ' OR '1' = '1extraer datos de la base de datos que no estaban destinados a ser accesibles. En ciertas situaciones, es posible inyectar subconsultas, sin embargo, aún no es posible modificar la base de datos de esta manera.
DaveRandom
64

Esta respuesta está escrita para mostrar cuán trivial es eludir el código de validación de usuario PHP mal escrito, cómo (y usando qué) funcionan estos ataques y cómo reemplazar las antiguas funciones de MySQL con una declaración preparada segura, y básicamente, por qué los usuarios de StackOverflow (probablemente con mucha repetición) están ladrando a los nuevos usuarios que hacen preguntas para mejorar su código.

En primer lugar, siéntase libre de crear esta base de datos mysql de prueba (he llamado preparación de mina):

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)

Una vez hecho esto, podemos pasar a nuestro código PHP.

Supongamos que el siguiente script es el proceso de verificación para un administrador en un sitio web (simplificado pero funcionando si lo copia y lo usa para las pruebas):

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

Parece lo suficientemente legítimo a primera vista.

El usuario tiene que ingresar un nombre de usuario y contraseña, ¿verdad?

Brillante, no ingrese en lo siguiente:

user: bob
pass: somePass

y envíalo.

La salida es la siguiente:

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

¡Súper! Trabajando como se esperaba, ahora intentemos con el nombre de usuario y contraseña reales:

user: Fluffeh
pass: mypass

¡Asombroso! Hola, todo el año, el código verificó correctamente un administrador. ¡Es perfecto!

Bueno en realidad no. Digamos que el usuario es una pequeña persona inteligente. Digamos que la persona soy yo.

Ingrese lo siguiente:

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

Y la salida es:

The check passed. We have a verified admin!

Enhorabuena, solo me permitiste ingresar a tu sección de administradores súper protegidos y yo ingresé un nombre de usuario falso y una contraseña falsa. En serio, si no me cree, cree la base de datos con el código que proporcioné y ejecute este código PHP, que a simple vista REALMENTE parece verificar el nombre de usuario y la contraseña bastante bien.

Entonces, en respuesta, ES POR ESO QUE SE TE GRITA.

Entonces, echemos un vistazo a lo que salió mal y por qué acabo de entrar en su cueva de súper administrador solo murciélago. Adiviné y supuse que no estaba siendo cuidadoso con sus entradas y simplemente las pasé directamente a la base de datos. Construí la entrada de una manera que CAMBIARÍA la consulta que realmente estaba ejecutando. Entonces, ¿qué se suponía que era y qué terminó siendo?

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

Esa es la consulta, pero cuando reemplazamos las variables con las entradas reales que utilizamos, obtenemos lo siguiente:

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

¿Ves cómo construí mi "contraseña" para que primero cerrara la comilla simple alrededor de la contraseña y luego introdujera una comparación completamente nueva? Luego, solo por seguridad, agregué otra "cadena" para que la comilla simple se cerrara como se esperaba en el código que teníamos originalmente.

Sin embargo, no se trata de que la gente te grite ahora, se trata de mostrarte cómo hacer que tu código sea más seguro.

Bien, entonces, ¿qué salió mal y cómo podemos solucionarlo?

Este es un ataque de inyección SQL clásico. Uno de los más simples para el caso. En la escala de los vectores de ataque, este es un niño que ataca un tanque y gana.

Entonces, ¿cómo protegemos su sagrada sección de administración y la hacemos agradable y segura? Lo primero que debe hacer es dejar de usar esas mysql_*funciones realmente antiguas y obsoletas . Lo sé, seguiste un tutorial que encontraste en línea y funciona, pero es viejo, está desactualizado y en el lapso de unos minutos, lo acabo de pasar sin siquiera sudar.

Ahora, tiene las mejores opciones para usar mysqli_ o PDO . Personalmente soy un gran admirador de PDO, por lo que usaré PDO en el resto de esta respuesta. Hay ventajas y desventajas, pero personalmente considero que las ventajas superan con creces a las desventajas. Es portátil a través de múltiples motores de base de datos, ya sea que esté utilizando MySQL u Oracle o casi cualquier cosa, simplemente cambiando la cadena de conexión, tiene todas las características elegantes que queremos usar y es agradable y limpio. Me gusta limpiar

Ahora, echemos un vistazo a ese código nuevamente, esta vez escrito usando un objeto 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>

Las principales diferencias son que no hay más mysql_*funciones. Todo se hace a través de un objeto PDO, en segundo lugar, está utilizando una declaración preparada. Ahora, ¿qué es una declaración preparatoria que preguntas? Es una manera de decirle a la base de datos antes de ejecutar una consulta, cuál es la consulta que vamos a ejecutar. En este caso, le decimos a la base de datos: "Hola, voy a ejecutar una instrucción select que quiera id, userid y pass de los usuarios de la tabla donde el userid es una variable y el pass también es una variable".

Luego, en la instrucción de ejecución, pasamos a la base de datos una matriz con todas las variables que ahora espera.

Los resultados son fantasticos. Probemos nuevamente esas combinaciones de nombre de usuario y contraseña:

user: bob
pass: somePass

El usuario no fue verificado. Increíble.

Qué tal si:

user: Fluffeh
pass: mypass

Oh, me emocioné un poco, funcionó: el cheque pasó. ¡Tenemos un administrador verificado!

Ahora, intentemos los datos que ingresaría un tipo inteligente para intentar pasar nuestro pequeño sistema de verificación:

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

Esta vez, obtenemos lo siguiente:

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

Esta es la razón por la que se te grita cuando publicas preguntas: es porque la gente puede ver que tu código se puede omitir sin siquiera intentarlo. Por favor, use esta pregunta y respuesta para mejorar su código, hacerlo más seguro y usar funciones actuales.

Por último, esto no quiere decir que este sea un código PERFECTO. Hay muchas más cosas que podría hacer para mejorarlo, use contraseñas con hash, por ejemplo, asegúrese de que cuando almacene información sensible en la base de datos, no la almacene en texto plano, tenga múltiples niveles de verificación, pero realmente, si solo cambia su antiguo código propenso a la inyección a esto, estará BIEN en el camino para escribir un buen código, y el hecho de que haya llegado tan lejos y aún esté leyendo me da la esperanza de que no solo implementará este tipo de código al escribir sus sitios web y aplicaciones, pero que podría salir e investigar esas otras cosas que acabo de mencionar, y más. Escriba el mejor código que pueda, no el código más básico que apenas funciona.

Fluffeh
fuente
2
¡Gracias por su respuesta! ¡Ten mi +1! Vale la pena señalar que mysql_*en sí mismo no es inseguro, pero promueve código inseguro a través de malos tutoriales y la falta de una declaración adecuada prepara API.
El fantasma de Madara
2
contraseñas sin etiqueta, ¡oh, el horror! = oP De lo contrario +1 para una explicación detallada.
críptico ツ
33

La extensión MySQL es la más antigua de las tres y fue la forma original en que los desarrolladores solían comunicarse con MySQL. Esta extensión ahora está en desuso en favor de las otras dos alternativas debido a las mejoras realizadas en las versiones más recientes de PHP y MySQL.

  • MySQLi es la extensión 'mejorada' para trabajar con bases de datos MySQL. Aprovecha las funciones que están disponibles en las versiones más recientes del servidor MySQL, expone una interfaz orientada a funciones y orientada a objetos al desarrollador y hace algunas otras cosas ingeniosas.

  • PDO ofrece una API que consolida la mayor parte de la funcionalidad que anteriormente se extendía entre las principales extensiones de acceso a la base de datos, es decir, MySQL, PostgreSQL, SQLite, MSSQL, etc. La interfaz expone objetos de alto nivel para que el programador trabaje con conexiones de base de datos, consultas y conjuntos de resultados y controladores de bajo nivel realizan comunicación y manejo de recursos con el servidor de base de datos. Se está discutiendo y trabajando mucho en DOP y se considera el método apropiado para trabajar con bases de datos en código moderno y profesional.

Alejandro
fuente
21

Las respuestas anteriores me parecen muy largas, así que para resumir:

La extensión mysqli tiene una serie de beneficios, siendo las mejoras clave sobre la extensión mysql:

  • Interfaz orientada a objetos
  • Soporte para declaraciones preparadas
  • Soporte para múltiples declaraciones
  • Soporte para transacciones
  • Capacidades mejoradas de depuración
  • Soporte de servidor incorporado

Fuente: descripción general de MySQLi


Como se explica en las respuestas anteriores, las alternativas a mysql son mysqli y PDO (PHP Data Objects).

  • API admite declaraciones preparadas del lado del servidor: compatible con MYSQLi y PDO
  • API admite declaraciones preparadas del lado del cliente: compatible solo con PDO
  • API admite procedimientos almacenados: tanto MySQLi como PDO
  • API admite múltiples declaraciones y toda la funcionalidad MySQL 4.1+: compatible con MySQLi y principalmente con PDO

Tanto MySQLi como PDO se introdujeron en PHP 5.0, mientras que MySQL se introdujo antes de PHP 3.0. Un punto a tener en cuenta es que MySQL está incluido en PHP5.x, aunque está en desuso en versiones posteriores.

Ani Menon
fuente
2
Su respuesta es demasiado larga, mientras que el resumen real es "mysql ext ya no existe". Eso es todo
su sentido común
1
@YourCommonSense Mi respuesta es por qué mysqli reemplazó a mysql. El punto no es decir que Mysqli existe hoy, así que úsalo. ¡Todos lo saben!
Ani Menon
1
Bueno, aparte del hecho de que nadie preguntó por qué mysqli reemplazó a mysql, tampoco responde a esta pregunta. Responde por qué se introdujo mysqli. Pero no explica por qué mysql y mysqli no podían vivir en paralelo
su sentido común el
@YourCommonSense También la pregunta del OP es "¿Por qué debería usar otra cosa incluso si funcionan en mi sitio?" y esa es la razón por la que señalé los cambios y mejoras. Puedes ver todas las otras respuestas, son largas, así que pensé que debería resumirlo.
Ani Menon
6

Es posible definir casi todas las mysql_*funciones usando mysqli o PDO. Simplemente inclúyalos en la parte superior de su antigua aplicación PHP, y funcionará en PHP7. Mi solución aquí .

<?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;
}
Pavel Tzonkov
fuente
En lugar de mostrar un enlace para su solución, agréguelos aquí como respuesta.
amarnath
1

Las funciones que son tan similares a este mysql_connect(), mysql_query()tipo son la versión anterior funciones PHP es decir (PHP 4) y ahora no está en uso.

Estos son reemplazados por mysqli_connect(), de mysqli_query()manera similar en el último PHP5.

Esta es la razón detrás del error.

Asesino
fuente
2
PHP 5 no ha sido el último en más de 2 años.
Madara Ghost
1

MySQL desaprobado en PHP 5.5.0 y eliminado en PHP 7.0.0. Para una aplicación grande y antigua, es difícil buscar y reemplazar cada función.

Podemos usar las funciones de MySQL creando una función de contenedor para cada uno de los siguientes códigos de ejecución. haga clic aquí

Vin
fuente
-9

las funciones de mysql_ * quedaron en desuso (a partir de PHP 5.5 ) dado el hecho de que se desarrollaron mejores funciones y estructuras de código. El hecho de que la función haya quedado en desuso significa que no se hará más esfuerzo para mejorarla en términos de rendimiento y seguridad, lo que significa que es menos a prueba de futuro .

Si necesitas más razones:

  • Las funciones mysql_ * no admiten declaraciones preparadas.
  • Las funciones mysql_ * no admiten la vinculación de parámetros.
  • Las funciones mysql_ * carecen de funcionalidad para la programación orientada a objetos.
  • la lista continua ...
Webeng
fuente
18
Esta respuesta está desactualizada. Además, no agrega nada útil a las respuestas que ya existen.
Su sentido común