PHP Prevención de la inyección SQL con consultas parametrizadas


Ejemplo

La inyección SQL es un tipo de ataque que permite a un usuario malintencionado modificar la consulta SQL, agregándole comandos no deseados. Por ejemplo, el siguiente código es vulnerable :

// Do not use this vulnerable code!
$sql = 'SELECT name, email, user_level FROM users WHERE userID = ' . $_GET['user'];
$conn->query($sql);

Esto permite a cualquier usuario de este script modificar nuestra base de datos básicamente a voluntad. Por ejemplo, considere la siguiente cadena de consulta:

page.php?user=0;%20TRUNCATE%20TABLE%20users;

Esto hace que nuestra consulta de ejemplo se vea así.

SELECT name, email, user_level FROM users WHERE userID = 0; TRUNCATE TABLE users;

Si bien este es un ejemplo extremo (la mayoría de los ataques de inyección de SQL no pretenden eliminar datos, ni la mayoría de las funciones de ejecución de consultas de PHP son compatibles con las consultas múltiples), este es un ejemplo de cómo un ataque de inyección de SQL puede ser posible por el montaje descuidado de la consulta. Desafortunadamente, los ataques de este tipo son muy comunes y son altamente efectivos debido a los codificadores que no toman las precauciones adecuadas para proteger sus datos.

Para evitar que se produzca la inyección de SQL, las declaraciones preparadas son la solución recomendada. En lugar de concatenar datos de usuario directamente a la consulta, se utiliza un marcador de posición en su lugar. Los datos se envían por separado, lo que significa que no hay posibilidad de que el motor de SQL confunda los datos del usuario para un conjunto de instrucciones.

Si bien el tema aquí es PDO, tenga en cuenta que la extensión MySQLi de PHP también admite declaraciones preparadas

PDO admite dos tipos de marcadores de posición (los marcadores de posición no se pueden usar para nombres de columnas o tablas, solo valores):

  1. Nombrados marcadores de posición. Un colon ( : ), seguido por un nombre distinto (por ejemplo. :user )

    // using named placeholders
    $sql = 'SELECT name, email, user_level FROM users WHERE userID = :user';
    $prep = $conn->prepare($sql);
    $prep->execute(['user' => $_GET['user']]); // associative array
    $result = $prep->fetchAll();
    
  2. Marcadores de posición posicionales de SQL tradicionales, representados como ? :

    // using question-mark placeholders
    $sql = 'SELECT name, user_level FROM users WHERE userID = ? AND user_level = ?';
    $prep = $conn->prepare($sql);
    $prep->execute([$_GET['user'], $_GET['user_level']]); // indexed array
    $result = $prep->fetchAll();
    

Si alguna vez necesita cambiar dinámicamente los nombres de tablas o columnas, sepa que esto es responsabilidad de su propio riesgo y una mala práctica. Sin embargo, se puede hacer por concatenación de cuerdas. Una forma de mejorar la seguridad de dichas consultas es establecer una tabla de valores permitidos y comparar el valor que desea concatenar con esta tabla.

Tenga en cuenta que es importante establecer el conjunto de caracteres de la conexión solo a través de DSN, de lo contrario, su aplicación podría estar expuesta a una vulnerabilidad poco clara si se utiliza alguna codificación impar. Para versiones PDO anteriores a 5.3.6, la configuración del conjunto de caracteres a través de DSN no está disponible y, por lo tanto, la única opción es establecer el atributo PDO::ATTR_EMULATE_PREPARES en false en la conexión inmediatamente después de su creación.

$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Esto hace que PDO utilice las declaraciones preparadas nativas del DBMS subyacente en lugar de simplemente emularlo.

Sin embargo, tenga en cuenta que la DOP retrocederá silenciosamente para emular las declaraciones que MySQL no puede preparar de forma nativa: las que pueden aparecer en el manual ( fuente ).