Inyección SQL: la importancia de proteger tu sistema de base de datos

Los sistemas de bases de datos son esenciales para grandes y pequeñas empresas. Estos prácticos sistemas de almacenamiento son utilizados por bancos y compañías de seguros para guardar información de cuentas y gestionar reservas de manera estructurada. Muchas empresas utilizan sistemas de planificación de recursos empresariales (ERP por sus siglas en inglés) para integrar y manejar sus operaciones, las cuales no funcionarían correctamente sin bases de datos. De la misma forma, la mayoría de las ofertas en Internet no serían posibles sin los sistemas de bases de datos. Crear y mantener estos archivadores electrónicos implica un gran esfuerzo, pero garantizar su seguridad representa el mayor reto. Los procedimientos para realizar copias de seguridad y la implementación de hardware a prueba de fallos son tan importantes como las medidas de protección contra ataques externos. Las llamadas inyecciones SQL, especialmente, representan una gran amenaza para el modelo tradicional de base de datos relacional y la información allí implementada.

¿Qué es la inyección SQL?

Una inyección SQL (del inglés SQL injection) se define como la explotación de una vulnerabilidad en los sistemas de bases relacionales accediendo a sus datos por medio del lenguaje SQL. El atacante se aprovecha de aquellos fallos de seguridad en la superficie de la base de datos que no han sido correctamente enmascarados y que contienen metacaracteres como el guion doble, las comillas o el punto y coma. Estos caracteres representan funciones especiales para el intérprete de SQL y permiten la influencia externa sobre las instrucciones ejecutadas. Es común que las inyecciones SQL se realicen en conjunto con programas PGP y ASP, que dependen de interfaces más antiguas. Allí, algunas de las entradas no cuentan con el correcto enmascaramiento, convirtiéndose así en el blanco perfecto para estos ataques.

El uso específico de metacaracteres permite a usuarios no autorizados acceder a otros comandos SQL y manipular las entradas para modificar, borrar o leer datos. En el peor de los casos es posible que, a través de una inyección directa de comandos SQL, un atacante obtenga acceso a la línea de comandos del sistema de ejecución de comandos y, por lo tanto, a toda la base de datos del servidor.

Ejemplos de inyección SQL: así funcionan los ataques a las bases de datos

Debido a que los servidores de bases de datos pueden ser rastreados fácilmente, de la misma forma que también es fácil ejecutar ataques de inyección SQL, este método se ha convertido rápidamente en uno de los preferidos por cibercriminales alrededor del mundo. Estos actúan con diferentes patrones de ataque y se aprovechan, sobretodo, de las vulnerabilidades tradicionales y actuales de las aplicaciones que participan en el proceso de gestión de datos. A continuación, y para explicar cómo funcionan estos ataques, hemos recogido algunos ejemplos de los métodos más comunes para aplicar una inyección SQL. 

Ejemplo 1: Acceso a través de una entrada del usuario mal enmascarada

Por lo general, antes de que un usuario pueda acceder a una base de datos necesita autenticarse. Para este propósito existen scripts que, por ejemplo, crean un formulario de inicio de sesión compuesto por un nombre de usuario y una contraseña. Así, el usuario rellena el formulario y el script se encarga de verificar si dicha entrada existe dentro de la base de datos. Por defecto, la base de datos cuenta con una tabla con el nombre “users” así como con las líneas para “username” y “password”. Para cualquier aplicación web, las líneas de comando (pseudocódigo) que permiten el acceso al servidor web serían:

uname = request.POST['username']
passwd = request.POST['password']

sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"

database.execute(sql)

Entre otras cosas, con una inyección SQL, el atacante tiene la capacidad de manipular el campo destinado a la contraseña escribiendo password' OR 1='1, lo que resultaría en la siguiente consulta SQL:

sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'

Así, el atacante obtiene acceso al registro completo de usuarios en la base de datos, pues la contraseña siempre es verdadera (1='1'). Ahora se puede registrar como administrador y puede realizar cambios en las entradas. El campo de nombre de usuario también puede manipularse de la misma forma.

Ejemplo 2: Espionaje de datos a través de la manipulación de ID

Solicitar información de una base de datos con un identificador es un método práctico y habitual, así como una potencial puerta de entrada para un ataque de inyección SQL. Por ejemplo, el identificador transmitido en una URL permite al servidor web identificar qué información debe recuperar de la base de datos. El script PHP para ello se verá de la siguiente manera:

<?php 
  #Datenbankabfrage anhand einer ID 
  $id = $_REQUEST['id'];
  $result = mysql_query("SELECT * from tabelle WHERE id=$id");
  
  # Anzeige des Ergebnisse ...
   ?>

La URL esperada tendría la forma …/script.php?id=22. En este caso, se mostrarían las entradas de la tabla con el ID 22. Ahora bien, si un usuario externo tiene la oportunidad de manipular el URL solicitado y, a cambio, le envía la solicitud …/script.php?id=22+or+1=1, estaría provocando que la solicitud mysql_query resultante muestre todos los datos y no solo la entrada con el ID 22.

SELECT * FROM tabelle WHERE id=22 or 1=1

¿Cómo identifican los atacantes bases de datos vulnerables?

Debido a que es común que los fabricantes de los programas que se comunican con las bases de datos no ofrezcan un nivel de seguridad suficiente, principalmente, toda página y aplicación web que utilice SQL como lenguaje de base de datos es vulnerable a ataques de inyección SQL. Adicionalmente, las vulnerabilidades descubiertas no suelen ser un secreto para la comunidad de usuarios en Internet, lo que ha generado la creación de páginas web que recopilan información sobre dichas vulnerabilidades y que, a la vez, explican a los cibercriminales cómo encontrar proyectos web para ello a través de la búsqueda de Google. Los mensajes de error estándar permiten comprobar rápidamente si los resultados especificados representan un objetivo potencial. A continuación te mostramos cómo:

[Domainname].de/news.php?id=5‘

Un simple apóstrofe en el identificador del URL hace que una página web vulnerable arroje un mensaje de error como este:

Query failed: You have an error in your SQL Syntax …”

En español: “Error en la solicitud: la sintaxis de tu SQL es incorrecta…”. Utilizando métodos similares también es posible obtener información como el número de columnas, los nombres de las tablas y las columnas, la versión de SQL e incluso los nombres de usuario y sus contraseñas. Existe una gran cantidad de herramientas gratuitas que permiten buscar y automatizar la inyección directa de comandos maliciosos en SQL. En cualquier caso, quien se aproveche de estas vulnerabilidades puede incurrir en castigos legales, a menos que se trate de bases de datos propias y que su manipulación se haga con fines preventivos y para controlar su seguridad.

Cómo proteger tu base de datos de una inyección SQL

Existen diferentes medidas para prevenir ataques de inyección SQL y proteger la base de datos. Para ello es necesario familiarizarse con todos los componentes involucrados: el servidor, las aplicaciones individuales y el sistema de gestión de base de datos.

Paso 1: Supervisar las modificaciones automáticas en las aplicaciones

Comprueba y filtra los métodos y parámetros que utilizan las aplicaciones para acceder a la base de datos. Los datos transferidos siempre deben estar en el formato de datos esperado. Si, por ejemplo, se requiere un parámetro numérico, lo puedes comprobar mediante un script PHP incluyendo la función is_numeric(). El proceso de filtrado se encarga de ignorar caracteres especiales. Otro punto importante es asegurarse de que las aplicaciones no generen ningún mensaje de error externo que pueda revelar información sobre el sistema o la estructura de la base de datos utilizada.

Los llamados Prepared Statements se han convertido en una práctica común que puede utilizarse con muchos sistemas de gestión de bases de datos. Estas instrucciones predefinidas se crearon originalmente para ejecutar solicitudes frecuentes, reduciendo el riesgo de ataques de inyección SQL debido a su estructura.  Esto principalmente debido a que las instrucciones con parámetros transmiten el comando SQL real a la base de datos, separado de los parámetros. Así, es el propio sistema de gestión de la base de datos quien, al final, los ejecuta juntos y enmascara automáticamente los caracteres especiales.

Paso 2: Proporcionar una protección completa al servidor

Lógicamente, la seguridad del servidor en el que se ejecuta el sistema de gestión de base de datos juega un papel fundamental en la prevención de ataques de inyección SQL. La primera medida de seguridad es el endurecimiento del sistema operativo de acuerdo con el siguiente patrón:

  • Instala y activa únicamente aquellas aplicaciones y servicios que son realmente relevantes para la base de datos.
  • Elimina todas las cuentas de usuario que no sean necesarias.
  • Asegúrate de instalar todas las actualizaciones relevantes para el sistema y el programa.

Cuanto más altos sean los requisitos vinculados a la seguridad de tu proyecto web, mayor será la necesidad de instalar sistemas de detección de intrusiones (IDS) o sistemas de prevención de intrusiones (IPS). Estos funcionan con diferentes sistemas de reconocimiento y tienen como objetivo detectar ataques contra el servidor a tiempo y alertar a su administrador y, en el caso de los IPS, tomar medidas apropiadas automáticamente. Otra medida de protección recomendable también es la implementación de un puerto en la capa de aplicación (Application Layer Gateway) que monitoree directamente, en el nivel de aplicación, el tráfico entre las aplicaciones y el navegador web.

Paso 3: Blindar la base de datos y utilizar códigos más seguros

De la misma forma que un sistema operativo, una base de datos debería estar libre de factores externos irrelevantes y ser actualizada regularmente. Para ello es recomendable desactivar todos los servicios y cuentas de usuario inactivos, así como eliminar todos los procedimientos almacenados que no sean necesarios. Configura una cuenta de base de datos que esté destinada únicamente para el acceso desde la web y que tenga derechos de acceso mínimos. Recuerda almacenar toda la información confidencial, por ejemplo, las contraseñas, de manera cifrada en la base de datos.

En lo que respecta a los Prepared Statements no se recomienda utilizar el módulo mysql de PHP, sino más bien mysqli o PDO. De esta manera puedes proteger tu sistema con códigos de seguridad. Así, por ejemplo, la función en script PHP mysqli_real_escape_string() previene que los caracteres especiales sean transmitidos a la base de datos SQL en su forma original y los enmascara por sí misma. Si expandes, por ejemplo, el siguiente código:

$query = "SELECT * FROM users 
WHERE username= '" . $_POST['username'] . "' 
AND password= '" . $_POST['password'] . "'";

con esta función:

$query = "SELECT * FROM users 
WHERE username= '" . mysqli_real_escape_string($_POST['username']) . "' 
AND password= '" . mysql_real_escape_string($_POST['password']) . "'";

se reemplazarán los caracteres problemáticos con la variante SQL segura (\').

La relación entre las Bobby Tables y los ataques de inyección SQL

La página web bobby-tables.com ha abordado el tema de la infiltración de información por parte de un usuario no autorizado en una base de datos de una manera muy peculiar: con el cómic web xkcd [Visita el webcomic xkcd sobre las inyecciones SQL] (http://bobby-tables.com/). El cómic muestra a una madre que recibe una llamada de la escuela de su hijo, llamado amorosamente Bobby Tables. El supuesto maestro le pregunta si, de hecho, su hijo se llama “Robert'); DROP TABLE Students;– –” y, al final, entiende la razón de la llamada: el intento de crear una entrada en la base de datos de estudiantes con el nombre de Robert generó la pérdida total de la información de la base de datos. La madre de Robert no puede hacer más que transmitirle unas pocas palabras de aliento y expresa su esperanza de que la escuela haya aprendido de sus errores y de que, en un futuro, se encargue de limpiar las entradas de su base de datos.

El cómic está destinado a ilustrar las fatales consecuencias que puede representar el acceso inautorizado o no controlado de un usuario para una base de datos. En el caso de la base de datos de la escuela, sucedió más o menos lo siguiente:

Los nombres de los estudiantes se guardan en una tabla llamada Students. Cada vez que un nuevo estudiante llega a la escuela, su nombre se introduce en dicha tabla utilizando el siguiente código:

$sql = "INSERT INTO Students (Name) VALUES ('" . $studentName . "');";
execute_sql($sql);

Se trata de un comando ordinario SQL-INSERT que añade el contenido de la variable $studentName en la tabla Students. La segunda parte del código presenta la secuencia (execute_sql) a la base de datos. Así, para un estudiante con el nombre Paul, la secuencia SQL funcionará de la misma manera. El siguiente código incluirá el nombre de Paul en la tabla Students:

INSERT INTO Students (Name) VALUES ('Paul');

Utilizando el mismo comando para incluir a Bobby Tables en la base de datos resultaría la siguiente línea de código:

INSERT INTO Students (Name) VALUES ('Robert'); DROP TABLE Students;––');

Aunque de esta forma se agregó la entrada Roberts a la tabla, la instrucción DROP TABLE se encargó de que la tabla fuera eliminada completamente. Debido a esta entrada no enmascarada en la base de datos, la madre (quien se esconde detrás del nombre del pequeño Bobby Tables) pudo llevar a cabo una inyección SQL con éxito.

Sin embargo, la solución de “mantener el código limpio manualmente” propuesta en el cómic no es infalible. Debido a que el enmascaramiento manual de caracteres es propenso a errores, para proteger a tu base de datos de ataques engañosos e inyecciones SQL es recomendable utilizar alguna de las soluciones mencionadas anteriormente, ya sean instrucciones con parámetros o funciones enmascaradas como mysqli_real_escape_string().