¿Qué es la inyección SQL y cómo prevenirla en aplicaciones PHP?

Entonces, ¿cree que su base de datos SQL es eficaz y está a salvo de la destrucción instantánea? Bueno, ¡SQL Injection no está de acuerdo!

Sí, estamos hablando de destrucción instantánea, porque no quiero abrir este artículo con la terminología poco convincente de "reforzar la seguridad" y "prevenir el acceso malicioso". SQL Injection Es un truco tan antiguo en el libro que todos, todos los desarrolladores, lo conocen muy bien y saben cómo prevenirlo. Excepto por ese momento extraño en el que se equivocan, y los resultados pueden ser nada menos que desastrosos.

Si ya sabe qué es SQL Injection, no dude en pasar a la segunda mitad del artículo. Pero para aquellos que recién están surgiendo en el campo del desarrollo web y sueñan con asumir roles más altos, es necesario hacer alguna introducción.

What is SQL Injection?

La clave para entender la inyección SQL está en su nombre: SQL + Inyección. La palabra "inyección" aquí no tiene connotaciones médicas, sino que es el uso del verbo "inyectar". Juntas, estas dos palabras transmiten la idea de poner SQL en una aplicación web.

Poner SQL en una aplicación web. . . hmmm. . . ¿No es eso lo que estamos haciendo de todos modos? Sí, pero no queremos que un atacante controle nuestra base de datos. Entendamos eso con la ayuda de un ejemplo.

Supongamos que está creando un sitio web PHP típico para una tienda de comercio electrónico local, por lo que decide agregar un formulario de contacto como este:

<form action="record_message.php" method="POST">   <label>Your name</label>   <input type="text" name="name">      <label>Your message</label>   <textarea name="message" rows="5"></textarea>      <input type="submit" value="Send"> </form>

Y asumamos el archivo send_message.php almacena todo en una base de datos para que los propietarios de la tienda puedan leer los mensajes de los usuarios más adelante. Puede tener algún código como este:

<?php  $name = $_POST['name']; $message = $_POST['message'];  // check if this user already has a message mysqli_query($conn, "SELECT * from messages where name = $name");  // Other code here

Entonces, primero está tratando de ver si este usuario ya tiene un mensaje no leído. La consulta SELECT * from messages where name = $name parece bastante simple, ¿verdad?

¡INCORRECTO!

En nuestra inocencia, hemos abierto las puertas a la destrucción instantánea de nuestra base de datos. Para que esto suceda, el atacante debe cumplir las siguientes condiciones:

El tercer punto significa que ahora que el atacante sabe que tiene una tienda de comercio electrónico, es muy probable que esté almacenando los datos del pedido en un orders mesa. Armado con todo esto, todo lo que el atacante necesita hacer es proporcionar esto como su nombre:

Joe; truncate orders;? ¡Sí señor! Veamos en qué se convertirá la consulta cuando sea ejecutada por el script PHP:

SELECT * FROM messages WHERE name = Joe; truncate orders;

De acuerdo, la primera parte de la consulta tiene un error de sintaxis (no hay comillas alrededor de "Joe"), pero el punto y coma obliga al motor MySQL a comenzar a interpretar uno nuevo: truncate orders. Así, de una sola vez, ¡todo el historial de pedidos se ha ido!

Ahora que sabe cómo funciona la inyección SQL, es hora de ver cómo detenerla. Las dos condiciones que deben cumplirse para una inyección SQL exitosa son:

  1. El script PHP debe tener privilegios de modificación / eliminación en la base de datos. Creo que esto es cierto para todas las aplicaciones y no podrá hacer que sus aplicaciones sean de solo lectura. 🙂 Y adivinen qué, incluso si eliminamos todos los privilegios de modificación, la inyección SQL aún puede permitir que alguien ejecute consultas SELECT y vea toda la base de datos, incluidos los datos confidenciales. En otras palabras, reducir el nivel de acceso a la base de datos no funciona y su aplicación lo necesita de todos modos.
  2. Se está procesando la entrada del usuario. La única forma en que puede funcionar la inyección SQL es cuando acepta datos de los usuarios. Una vez más, no es práctico detener todas las entradas de su aplicación solo porque le preocupa la inyección de SQL.

Preventing SQL injection in PHP

Ahora, dado que las conexiones de la base de datos, las consultas y las entradas del usuario son parte de la vida, ¿cómo podemos prevenir la inyección de SQL? Afortunadamente, es bastante simple y hay dos formas de hacerlo: 1) desinfectar la entrada del usuario y 2) usar declaraciones preparadas.

Desinfectar la entrada del usuario

Si está utilizando una versión anterior de PHP (5.5 o inferior, y esto sucede mucho en el alojamiento compartido), es aconsejable ejecutar toda la entrada del usuario a través de una función llamada mysql_real_escape_string(). Básicamente, lo que hace es eliminar todos los caracteres especiales de una cadena para que pierdan su significado cuando los utilice la base de datos.

Por ejemplo, si tiene una cadena como I'm a string, un atacante puede utilizar el carácter de comilla simple (') para manipular la consulta de la base de datos que se está creando y provocar una inyección SQL. Ejecutarlo a través mysql_real_escape_string()produce I\'m a string, que agrega una barra invertida a la comilla simple, evitándola. Como resultado, la cadena completa ahora se pasa como una cadena inofensiva a la base de datos, en lugar de poder participar en la manipulación de consultas.

Hay un inconveniente con este enfoque: es una técnica realmente antigua que acompaña a las formas más antiguas de acceso a bases de datos en PHP. A partir de PHP 7, esta función ya no existe, lo que nos lleva a nuestra próxima solución.

Utilice declaraciones preparadas

Las declaraciones preparadas son una forma de realizar consultas a bases de datos de manera más segura y confiable. La idea es que en lugar de enviar la consulta sin procesar a la base de datos, primero le decimos a la base de datos la estructura de la consulta que enviaremos. Esto es lo que queremos decir con "preparar" una declaración. Una vez que se prepara una declaración, pasamos la información como entradas parametrizadas para que la base de datos pueda "llenar los vacíos" conectando las entradas a la estructura de consulta que enviamos antes. Esto elimina cualquier poder especial que puedan tener las entradas, lo que hace que se traten como meras variables (o cargas útiles, si se quiere) en todo el proceso. Así es como se ven las declaraciones preparadas:

<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB";  // Create connection $conn = new mysqli($servername, $username, $password, $dbname);  // Check connection if ($conn->connect_error) {     die("Connection failed: " . $conn->connect_error); }  // prepare and bind $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email);  // set parameters and execute $firstname = "John"; $lastname = "Doe"; $email = "[email protected]"; $stmt->execute();  $firstname = "Mary"; $lastname = "Moe"; $email = "[email protected]"; $stmt->execute();  $firstname = "Julie"; $lastname = "Dooley"; $email = "[email protected]"; $stmt->execute();  echo "New records created successfully";  $stmt->close(); $conn->close(); ?>

Sé que el proceso suena innecesariamente complejo si eres nuevo en las declaraciones preparadas, pero el concepto bien vale la pena el esfuerzo. Esto es una buena introducción a ella.

Para aquellos que ya están familiarizados con la extensión PDO de PHP y la utilizan para crear declaraciones preparadas, tengo un pequeño consejo.

Advertencia: tenga cuidado al configurar PDO

Cuando usamos PDO para el acceso a la base de datos, podemos dejarnos llevar por una falsa sensación de seguridad. “Ah, bueno, estoy usando PDO. Ahora no necesito pensar en nada más ”, así es como funciona nuestro pensamiento en general. Es cierto que PDO (o declaraciones preparadas por MySQLi) es suficiente para evitar todo tipo de ataques de inyección SQL, pero debes tener cuidado al configurarlo. Es común copiar y pegar el código de los tutoriales o de sus proyectos anteriores y seguir adelante, pero esta configuración puede deshacer todo:

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

Lo que hace esta configuración es decirle a PDO que emule declaraciones preparadas en lugar de utilizar realmente la función de declaraciones preparadas de la base de datos. En consecuencia, PHP envía cadenas de consulta simples a la base de datos incluso si su código parece estar creando declaraciones preparadas y configurando parámetros y todo eso. En otras palabras, eres tan vulnerable a la inyección de SQL como antes. 🙂

La solución es simple: asegúrese de que esta emulación esté configurada como falsa.

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

Ahora, el script PHP se ve obligado a usar declaraciones preparadas a nivel de base de datos, lo que evita todo tipo de inyección SQL.

Preventing using WAF

¿Sabe que también puede proteger las aplicaciones web de la inyección de SQL utilizando WAF (firewall de aplicaciones web)?

Bueno, no solo la inyección de SQL, sino muchas otras vulnerabilidades de capa 7 como scripts entre sitios, autenticación rota, falsificación entre sitios, exposición de datos, etc. Seguridad Mod o basado en la nube como el siguiente.

SQL injection and modern PHP frameworks

La inyección SQL es tan común, tan fácil, tan frustrante y tan peligrosa que todos los Marcos web PHP vienen incorporados con contramedidas. En WordPress, por ejemplo, tenemos el $wpdb->prepare() , mientras que si está utilizando un marco MVC, hace todo el trabajo sucio por usted y ni siquiera tiene que pensar en prevenir la inyección de SQL. Es un poco molesto que en WordPress tengas que preparar declaraciones explícitamente, pero bueno, es WordPress de lo que estamos hablando. 🙂

De todos modos, mi punto es que la clase moderna de desarrolladores web no tiene que pensar en la inyección de SQL y, como resultado, ni siquiera son conscientes de la posibilidad. Como tal, incluso si dejan una puerta trasera abierta en su aplicación (tal vez sea un $_GET parámetro de consulta y los viejos hábitos de lanzar una consulta sucia se activan), los resultados pueden ser catastróficos. Por lo tanto, siempre es mejor tomarse el tiempo para profundizar en los cimientos.

En resumen

La inyección SQL es un ataque muy desagradable a una aplicación web, pero se evita fácilmente. Como vimos en este artículo, tener cuidado al procesar la entrada del usuario (por cierto, SQL Injection no es la única amenaza que trae el manejo de la entrada del usuario) y consultar la base de datos es todo lo que hay que hacer. Dicho esto, no siempre trabajamos en la seguridad de un marco web, por lo que es mejor estar al tanto de este tipo de ataque y no caer en la trampa.