¿Cómo puedo evitar la inyección SQL en PHP?

Si la entrada del usuario se inserta sin modificación en una consulta SQL, entonces la aplicación se vuelve vulnerable a la inyección SQL, como en el siguiente ejemplo:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Esto es porque el usuario puede introducir algo como value'); DROP TABLE table;--, y la consulta se convierte en:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

¿Qué se puede hacer para evitar que esto ocurra?

Advertencia de obsoleto: El código de ejemplo de esta respuesta (al igual que el código de ejemplo de la pregunta) utiliza la extensión MySQL de PHP, que fue obsoleta en PHP 5.5.0 y eliminada por completo en PHP 7.0.0.

Advertencia de seguridad: Esta respuesta no está en línea con las mejores prácticas de seguridad. Escapar es inadecuado para prevenir la inyección de SQL, use sentencias preparadas en su lugar. Utilice la estrategia descrita a continuación bajo su propio riesgo. (Además, mysql_real_escape_string() fue eliminado en PHP 7.)

Si está usando una versión reciente de PHP, la opción mysql_real_escape_string descrita a continuación ya no estará disponible (aunque mysqli::escape_string es un equivalente moderno). Hoy en día la opción mysql_real_escape_string sólo tendría sentido para el código heredado en una versión antigua de PHP.


Tiene dos opciones - escapar los caracteres especiales en su unsafe_variable, o usar una consulta parametrizada. Ambas te protegerán de la inyección SQL. La consulta parametrizada se considera la mejor práctica, pero requerirá cambiar a una extensión más reciente de MySQL en PHP antes de poder usarla.

Cubriremos primero el escape de cadenas de menor impacto.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Ver también, los detalles de la función mysql_real_escape_string.

Para utilizar la consulta parametrizada, es necesario utilizar MySQLi en lugar de las funciones MySQL. Para reescribir su ejemplo, necesitaríamos algo como lo siguiente.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

La función clave que querrás leer ahí sería mysqli::prepare.

También, como otros han sugerido, puedes encontrar útil/fácil subir una capa de abstracción con algo como PDO.

Ten en cuenta que el caso por el que preguntas es bastante sencillo y que casos más complejos pueden requerir enfoques más complejos. En particular:

  • Si quiere alterar la estructura del SQL basándose en la entrada del usuario, las consultas parametrizadas no van a ayudar, y el escape requerido no está cubierto por mysql_real_escape_string. En este tipo de casos, sería mejor pasar la entrada del usuario a través de una lista blanca para asegurar que sólo se permiten valores 'seguros'.
  • Si usas enteros de la entrada del usuario en una condición y tomas el enfoque mysql_real_escape_string, sufrirás el problema descrito por Polynomial en los comentarios de abajo. Este caso es más complicado porque los enteros no estarían rodeados de comillas, por lo que se podría solucionar validando que la entrada del usuario contenga sólo dígitos.
  • Es probable que haya otros casos que desconozco. Puede que encuentres en esto un recurso útil sobre algunos de los problemas más sutiles que puedes encontrar.
Comentarios (4)

Yo recomendaría usar PDO (PHP Data Objects) para ejecutar consultas SQL parametrizadas.

Esto no sólo protege contra la inyección de SQL, sino que también acelera las consultas.

Y al usar PDO en lugar de las funciones mysql_, mysqli_, y pgsql_, hace que su aplicación sea un poco más abstracta de la base de datos, en el raro caso de que tenga que cambiar de proveedor de base de datos.

Comentarios (3)

Podrías hacer algo básico como esto:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Esto no resolverá todos los problemas, pero es un muy buen paso. He omitido elementos obvios como la comprobación de la existencia de la variable, el formato (números, letras, etc.).

Comentarios (10)