¿Cree que su base de datos SQL es eficaz y está a salvo de una destrucción instantánea? Pues bien, ¡SQL Injection no está de acuerdo!

Sí, es destrucción instantánea de lo que estamos hablando, porque no quiero abrir este artículo con la terminología coja habitual de «reforzar la seguridad» y «prevenir el acceso malicioso» La inyección SQL es un truco tan viejo en el libro que todo el mundo, todos los desarrolladores, lo conocen muy bien y saben perfectamente cómo evitarlo. Excepto en alguna que otra ocasión en la que cometen un desliz, y los resultados pueden ser poco menos que desastrosos.

Si ya sabe lo que es la inyección SQL, no dude en pasar a la segunda mitad del artículo. Pero para aquellos que se están iniciando en el campo del desarrollo web y sueñan con ocupar puestos de mayor responsabilidad, es conveniente hacer alguna introducción.

¿Qué es la inyección SQL?

La clave para entender la Inyección SQL está en su nombre: Inyección SQL. La palabra «inyección» aquí no tiene ninguna connotación médica, sino que es el uso del verbo «inyectar» Juntas, estas dos palabras transmiten la idea de introducir 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 maneje nuestra base de datos. Vamos a entenderlo con la ayuda de un ejemplo.

Supongamos que está construyendo un sitio web PHP típico para una tienda local de comercio electrónico, así que decide añadir un formulario de contacto como este:

<form action="record_message.php" method="POST">
  <label>Su nombre</label>
  <input type="text" name="nombre">
  
  <label>Su mensaje</label>
  <textarea name="mensaje" rows="5"></textarea>
  
  <input type="submit" value="Enviar">
</form>

Y supongamos que 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

$nombre = $_POST['nombre'];
$mensaje = $_POST['mensaje'];

// comprobar si este usuario ya tiene un mensaje
mysqli_query($conn, "SELECT * from mensajes where nombre = $nombre");

// Otro código aquí

Así que primero está intentando ver si este usuario ya tiene un mensaje sin leer. La consulta SELECT * from mensajes where nombre = $nombre parece bastante sencilla, ¿verdad?

¡ERROR!

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

  • La aplicación se ejecuta en una base de datos SQL (hoy en día, casi todas las aplicaciones lo son)
  • La conexión actual a la base de datos tiene permisos de «edición» y «eliminación» en la base de datos
  • Se pueden adivinar los nombres de las tablas importantes

El tercer punto significa que ahora que el atacante sabe que usted ejecuta una tienda de comercio electrónico, es muy probable que almacene los datos de los pedidos en una tabla de pedidos. Armado con todo esto, todo lo que el atacante necesita hacer es proporcionar esto como su nombre:

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

SELECT * FROM mensajes WHERE nombre = Joe; truncar pedidos;

Vale, 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 empezar a interpretar una nueva: truncar pedidos. Así, de un plumazo, ¡todo el historial de pedidos ha desaparecido!

Ahora que ya sabe cómo funciona la inyección SQL, es hora de ver cómo detenerla. Las dos condiciones que deben cumplirse para que la inyección SQL tenga éxito 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 sólo lectura. 🙂 Y adivine qué, incluso si eliminamos todos los privilegios de modificación, la inyección SQL puede seguir permitiendo que alguien ejecute consultas SELECT y vea toda la base de datos, datos sensibles incluidos. 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. La entrada del usuario está siendo procesada. La única forma en que la inyección SQL puede funcionar es cuando está aceptando datos de los usuarios. Una vez más, no es práctico detener todas las entradas de su aplicación sólo porque le preocupe la inyección SQL.

Prevención de la inyección SQL en PHP

Ahora, dado que las conexiones a bases de datos, las consultas y las entradas de usuario forman parte de la vida, ¿cómo podemos prevenir la inyección SQL? Afortunadamente, es bastante simple, y hay dos maneras de hacerlo: 1) desinfectar la entrada del usuario, y 2) utilizar sentencias preparadas.

Sanear la entrada del usuario

Si está utilizando una versión antigua de PHP (5.5 o inferior, y esto ocurre mucho en los alojamientos compartidos), es aconsejable pasar toda la entrada del usuario por 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 al ser utilizados por la base de datos.

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

Hay un inconveniente con este enfoque: es una técnica muy, muy antigua que va de la mano con las formas más antiguas de acceso a bases de datos en PHP. A partir de PHP 7, esta función ya ni siquiera existe, lo que nos lleva a nuestra siguiente solución.

Utilice sentencias preparadas

Las sentencias preparadas son una forma de realizar consultas a la base de datos de forma más segura y fiable. La idea es que en lugar de enviar la consulta en bruto a la base de datos, primero le decimos a la base de datos la estructura de la consulta que vamos a enviar. Esto es lo que entendemos por «preparar» una sentencia. Una vez preparada una sentencia, pasamos la información como entradas parametrizadas para que la base de datos pueda «rellenar los huecos» conectando las entradas a la estructura de la consulta que enviamos antes. Esto les quita cualquier poder especial que pudieran tener las entradas, haciendo que sean tratadas como meras variables (o cargas útiles, si lo prefiere) en todo el proceso. Este es el aspecto de las sentencias preparadas

<?php
$servername = "localhost";
$nombredeusuario = "nombredeusuario";
$contraseña = "contraseña";
$nombredb = "miDB";

// Crear conexión
$conn = new mysqli($nombreservidor, $nombreusuario, $contraseña, $nombredb);

// Comprobar la conexión
if ($conn->connect_error) {
    die("Error de conexión: " . $conn->connect_error);
}

// preparar y enlazar
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $nombre, $apellido, $correo electrónico);

// establecer parámetros y ejecutar
$nombre = "John
$apellido = "Doe";
$email = "john@example.com";
$stmt->ejecutar();

$nombre = "Mary
$apellido = "Moe";
$email = "mary@example.com";
$stmt->ejecutar();

$nombre = "Julie
$apellido = "Dooley";
$email = "julie@example.com";
$stmt->ejecutar();

echo "Nuevos registros creados con éxito";

$stmt->close();
$conn->close();
?>

Sé que el proceso suena innecesariamente complejo si es nuevo en esto de las sentencias preparadas, pero el concepto merece la pena. Aquí tiene una buena introducción al tema.

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

Advertencia: Tenga cuidado al configurar PDO

Al utilizar PDO para el acceso a bases de datos, podemos caer en una falsa sensación de seguridad. «Ah, bueno, estoy usando PDO. Ahora no necesito pensar en nada más» — así es como generalmente va nuestro pensamiento. Es cierto que PDO (o las sentencias preparadas MySQLi) es suficiente para evitar todo tipo de ataques de inyección SQL, pero debe tener cuidado al configurarlo. Es común simplemente copiar-pegar código de tutoriales o de sus proyectos anteriores y seguir adelante, pero esta configuración puede deshacerlo todo:

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

Lo que hace este ajuste es decirle a PDO que emule sentencias preparadas en lugar de utilizar realmente la característica de sentencias preparadas de la base de datos. En consecuencia, PHP envía simples cadenas de consulta a la base de datos incluso si su código parece que está creando sentencias preparadas y estableciendo parámetros y todo eso. En otras palabras, usted es tan vulnerable a la inyección SQL como antes. 🙂

La solución es sencilla: asegúrese de que esta emulación está establecida en false.

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

Ahora el script PHP se ve obligado a utilizar sentencias preparadas a nivel de base de datos, evitando todo tipo de inyecciones SQL.

Prevención mediante WAF

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

Bueno, no sólo la inyección SQL, sino muchas otras vulnerabilidades de capa 7 como cross-site scripting, autenticación rota, cross-site forgery, exposición de datos, etc. Puede utilizar uno autoalojado como Mod Security o basado en la nube como el siguiente.

Inyección SQL y frameworks PHP modernos

La inyección SQL es tan común, tan fácil, tan frustrante y tan peligrosa que todos los frameworks web PHP modernos vienen incorporados con contramedidas. En WordPress, por ejemplo, tenemos la función $wpdb->prepare(), mientras que si utiliza un framework MVC, éste hace todo el trabajo sucio por usted y ni siquiera tiene que pensar en prevenir la inyección SQL. Es un poco molesto que en WordPress tenga que preparar las sentencias explícitamente, pero oye, es WordPress de lo que estamos hablando. 🙂

En cualquier caso, lo que quiero decir es que la raza moderna de desarrolladores web no tiene que pensar en la inyección 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 parámetro de consulta $_GET y los viejos hábitos de lanzar una consulta sucia entran en acción), los resultados pueden ser catastróficos. Así que siempre es mejor tomarse el tiempo necesario para profundizar en los fundamentos.

Conclusión

La inyección SQL es un ataque muy desagradable contra una aplicación web, pero es fácil de evitar. Como hemos visto en este artículo, tener cuidado al procesar la entrada del usuario (por cierto, la inyección SQL no es la única amenaza que conlleva el manejo de la entrada del usuario) y al consultar la base de datos es todo lo que hay que hacer. Dicho esto, no siempre trabajamos en la seguridad de un framework web, así que es mejor ser consciente de este tipo de ataque y no caer en él.