La inyección SQL no se va a ninguna parte


Las inyecciones SQL pueden parecer algo del pasado, pero en realidad sigue siendo uno de los métodos de ataque dirigidos más utilizados para aplicaciones web en todo el mundo. Según lo indicado en el informe de Akamai Media Under Assault, un asombroso 69.7% de todos los ataques a aplicaciones web entre enero de 2018 y junio de 2019 fueron inyecciones de SQL. Eso es MUCHÍSIMO teniendo en cuenta que supuestamente fue descubierto por primera vez por un hombre llamado Jeff “Rain Forrest Puppy” Forristal en 1998. Sí … ‘98.

Lo Básico

Los ataques de tipo inyección SQL pertenecen a la clase de ataque tipo inyección donde las entradas de fuentes de datos no confiables se utilizan (dinámicamente) para construir y ejecutar comandos como SQL, LDAP, shell, XML, XPATH y código. Los entornos de aplicación web modernos generalmente pueden evitar ataques de tipo inyección SQL, lo que a menudo les da a los desarrolladores web principiantes una falsa sensación de seguridad y les hace crear serias vulnerabilidades en sistemas críticos en vivo.

En el caso de las inyecciones SQL, el backend que ejecuta los datos creados es una base de datos relacional. El atacante envía datos diseñados a la aplicación que los maneja como comandos SQL en el lado del servidor. El reenvío de datos de una fuente no confiable a la base de datos SQL puede conducir a:

  • Fuga de datos;
  • Modificaciones no autorizadas de datos y pérdida de datos;
  • El atacante obtiene el control total del servidor objetivo;
  • Problemas de disponibilidad, integridad y confidencialidad.

Esto se produce cuando los datos ingresados por una persona que no es de confianza se utilizan dentro de las declaraciones SQL sin sanitizar, las propias declaraciones SQL se construyen dinámicamente o los tipos de datos SQL no se verifican ni se convierten correctamente.

Para explicar con más detalle, necesitamos ir aún más allá de lo básico…

SQL - Lenguaje de consulta estructurado

SQL es un lenguaje declarativo donde se describe (declara) el resultado necesario en lugar de describir un algoritmo como en lenguajes imperativos como JavaScript o PHP. El lenguaje SQL se utiliza para procesar datos en Sistemas de gestión de bases de datos relacionales (RDBMS). Muchas aplicaciones web que tienen HTML dinámico como interfaz utilizan una base de datos SQL para almacenar datos. Un RDBMS generalmente puede manejar varias bases de datos (o esquemas).

Comandos básicos:

# En el caso de MySQL puedes encontrar todos los nombres de esquema utilizando el siguiente comando:
show databases;

# Para usar una base de datos específica usa el siguiente comando:
use <databasename>;

# Los nombres de las tablas se pueden encontrar con el siguiente comando:
show tables;

# Puedes encontrar una lista de todas las columnas en una tabla usando el comando:
desc <tablename>;

Seleccionar comandos:

# La información se puede recuperar de las tablas con un comando select:
select column,list,separated,by,commas from schemaname.tablename where selection_criteria;

# Ejemplo:
select first_name, last_name from dvwa.users where user_id=1;

Tautología SQL

Una tautología es una afirmación que siempre es cierta. Por ejemplo: A o 1=1 siempre es cierto independientemente del valor de A. Consulta la siguiente instrucción SQL y toma en cuenta el o 1=1 al final de la línea.

select first_name,last_name from dvwa.users where user_id=1 or 1=1;

Esto hace que la cláusula where devuelva verdadero para cada fila y se seleccionan todos los usuarios de la tabla. A veces, los hackers necesitan consultas que no devuelven ningún dato, en ese caso se generará un falso en la cláusula where:

select ... where user_id=1 and 1=2;

Union Select

En las inyecciones de SQL, la palabra clave union es ampliamente utilizada para recuperar el contenido de una base de datos. Union permite componer un conjunto de resultados a partir de diferentes consultas. Existe una fuerte restricción en el número de columnas seleccionadas en cada consulta. Debe ser igual para cada consulta. Entonces, si seleccionas columnas de 2 tablas diferentes, luego necesitas agregar o eliminar columnas seleccionadas en cualquiera de las tablas para que coincida el número de columnas.

# Selección de 2 columnas de 2 tablas diferentes:
select columnA1, columnA2 from databaseA.tableA union select columnB1, columnB2 from databaseB.tableB;

Los usuarios malintencionados intentan extender las consultas utilizando unión select desde el esquema_de_información para revelar información privilegiada que puede usarse nuevamente con el próxima union select.

Comentarios en SQL

A veces, los hackers manipulan comandos SQL mediante el uso de caracteres de comentarios como # y -- para finalizar la consulta antes de lo que se supone que debe suceder.

MySQL admite tres estilos de comentarios:

  • # comenta todo hasta el final de la línea
  • -- comenta todo hasta el final de la línea
  • /* block comment */ se puede usar dentro del comando SQL para comentar fragmentos específicos

Ejemplos:

select TABLE_SCHEMA from information_schema.tables; # Este es un comentario
select TABLE_NAME from information_schema.tables; -- Este es un comentario
select TABLE_NAME,TABLE_SCHEMA /* Este es un comentario */ from
/*
este es un comentario de varias líneas
*/
information_schema.tables;

Comillas Simples en SQL

En la sintaxis SQL, el carácter ' (una comilla simple) indica el comienzo o el final de una cadena de datos. Por ejemplo, el SQL selecciona select 'sample text' as result; y devuelve el siguiente resultado:

+-------------+
| result      |
+-------------+
| sample text |
+-------------+
1 row in set (0,00 sec)

Intentar insertar una comilla simple en una consulta como select 'sample ' text' as result; da como resultado el siguiente error, que podría indicar que es posible una inyección (más sobre eso más adelante):

ERROR 1064 (42000): You have an error in your SQL syntax;
    check the manual that corresponds to your MySQL server version
    for the right syntax to use near '' as result;

Inyecciones SQL

Las inyecciones SQL vienen en muchas formas diferentes, dependiendo de lo que el usuario malicioso quiera lograr. Esto varía desde pasar por alto los formularios de autenticación hasta ejecutar comandos del sistema operativo.

Tipos de SQLi:

  • Inyección SQL basada en errores
  • Pasar por alto los formularios de autorización
  • Inyección SQL basada en union
  • Inyección SQL a ciegas
  • Comandos SQL encadenados
  • Inyecciones SQL dentro de insertar/actualizar/eliminar
  • Expresiones regulares en inyecciones SQL
  • Carga/escritura de archivos
  • Ejecución de comandos del sistema operativo
  • Uso de otros canales en lugar de métodos HTTP para inyecciones SQL

Inyección SQL basada en errores

Las inyecciones SQL basadas en errores se utilizan para revelar información sobre la existencia de vulnerabilidades de inyección SQL y el tipo de motor de base de datos. Por ejemplo, como se mencionó anteriormente, si al ingresar el carácter ' en el campo de formulario de un sitio web causa un error, entonces existe el error de inyección SQL. En el caso de MySQL, el resultado debería verse así:

...- You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version
for the right syntax to use near ...

Del mensaje de error podemos ver que MySQL se usa como la base de datos. Además, puedes obtener información sobre los nombres de campo y tabla utilizados en la consulta. La vulnerabilidad de inyección SQL basada en errores existe cuando los mensajes de error generados por el backend de la base de datos se devuelven al usuario final, que luego puede usar esa información para realizar otros tipos de ataques de inyección SQL.

Bypass de Autenticación de Inyección SQL

Las inyecciones SQL se pueden usar fácilmente para eludir la seguridad y obtener acceso a las aplicaciones web. Digamos que el siguiente formulario web te pide un nombre de usuario y contraseña:

<form action="?page=login" method="POST">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="submit" id="login">
</form>

Luego, se ejecuta el siguiente script PHP para autorizarte:

$query ="SELECT * FROM shop_users
WHERE username='$_POST['username']' and password='$_POST['password']'";
$result = mysqli_query($connection, $query)
$user=mysqli_fetch_assoc($result);
if (!empty($user)) {
return $user;

El nombre de usuario y la contraseña enviados se insertan directamente en la consulta SQL sin ningún tipo de sanitización de entrada. Si ingresas el nombre de usuario bob y la contraseña LetMePass, la siguiente consulta se ejecuta en el backend de la base de datos:

SELECT * FROM shop_users WHERE username='bob' and password='LetMepass';

Ahora, si configuras admin como tu nombre de usuario y usas una tautología SQL (mencionada anteriormente) en el campo de contraseña, acompañada de una comilla simple y un carácter de comentario para finalizar la consulta a la mitad, entonces puedes pasar por alto de forma muy ingeniosa la verificación de contraseña por completo.

Valor de ejemplo en el formulario:

randompassword' or '1'='1

Toma en cuenta que el código php agrega el último ' y el comando ejecutado por completo será:

SELECT * FROM shop_users WHERE username='admin' and password='randompassword' or '1'='1';

Si la consulta es exitosa, entonces se extrae el primer usuario del conjunto de datos y el hacker inicia sesión. Si ese primer usuario es administrador, el atacante ahora iniciará sesión con privilegios superiores. Pero si el primer usuario devuelto es un usuario no privilegiado, entonces el atacante puede ingresar un nombre de usuario aleatorio y usar la declaración falsa para excluir al primer usuario al ingresar lo siguiente en el campo de contraseña:

randompassword' or '1'='1' and id <> 1; -- #

Toma en cuenta que -- debe ir seguido de al menos un carácter (como un espacio) y si la tabla tiene un campo de id, la consulta devuelve el segundo usuario.

Inyecciones SQL basadas en union

Las inyecciones SQL basadas en union se utilizan para revelar nombres de esquemas de bases de datos, nombres de tablas y nombres de columnas. Esta información se puede usar para escribir otras consultas, por ejemplo, para volcar datos. Como se indicó anteriormente, union select es posible si cada selección tiene exactamente el mismo número de columnas en su conjunto de resultados. Cuando los formularios web permiten ingresar datos, muestran el resultado (como un formulario de búsqueda) y habilitan el uso de inyecciones SQL, entonces probablemente sea posible union select. Para averiguar el número correcto de columnas en una tabla, puedes usar un método de ensayo y error con la palabra clave order by. Cuando obtienes un error para order by 3, entonces habrá dos columnas.

Ejemplos:

a' order by 1; -- # 
a' order by 2; -- # 
a' order by 3; -- # 

Cuando conoces el número de columnas en una tabla, puedes usar union select en esa tabla. Sin embargo, primero debes conocer los nombres de esquema, tabla y columna. Tú conoces el esquema de información, por lo tanto, puedes recopilar información sobre tablas, esquemas y columnas. Termina la consulta en el campo de formulario con ' y agrega un union select terminado con los caracteres de comentario # --.

Ejemplo en caso de que la consulta original devuelva dos columnas:

' union select TABLE_NAME, TABLE_SCHEMA from information_schema.tables; # -- 

Ejemplo en caso de que la consulta original devuelva 4 columnas:

' union select 1,2,TABLE_NAME, COLUMN_NAME from information_schema.columns; # -- 

Al usar union select, es posible que debas seleccionar más campos de los que contiene la primera tabla. En ese caso, la función concat() se puede usar para combinar dos o más columnas en una columna de resultados. La función concat(arg1,...,argN) concatena todos los argumentos a un resultado.

Ejemplos:

# Normal select
select concat(version(),' ',user());

# Union select injection
' union select concat(version(),' ',user()); # -- 
' union select concat(TABLE_NAME,'-',TABLE_SCHEMA) from information_schema.tables; # --
' union select concat(TABLE_NAME,'-',COLUMN_NAME) from information_schema.columns; # --

Mitigación de Inyección SQL

Hay varias formas de proteger tus sistemas contra las inyecciones de SQL:

  • Usar Declaraciones Preparadas (con Consultas Parametrizadas)
  • Usar Procedimientos Almacenados
  • Validación de Entrada de Lista Blanca
  • Escapar todas las Entradas Proporcionadas por el Usuario
  • Aplicar el Principio de Mínimo Privilegio
  • Realizar la Validación de Entrada de Lista Blanca como Defensa Secundaria (detectar la entrada no autorizada antes de pasarla a la consulta SQL)

  • Reforzar su servidor http con medidas de seguridad (mod_security para Apache, directivas de configuración en Nginx, etc.)

En conclusión

Las inyecciones SQL son bastante fáciles de usar, ya que no requieren un mayor grado de programación o seguridad cibernética. Todo lo que necesitas es una comprensión básica de las consultas SQL y cómo se implementan en el código. Por extraño que parezca, sigue siendo una de las mayores vulnerabilidades de aplicaciones web en el mundo.

Roland Kaur