Inyección SQL, 21 años después 

By albert
16 Jul 2019

El 25 de diciembre de 1998, Jeff Forristal, bajo el seudónimo Rain Forest Puppy, realizó una publicación que exponía públicamente la primera vulnerabilidad de inyección SQL. 
21 años después, esta vulnerabilidad crítica, que en la mayoría de los casos tiene fácil solución, sigue siendo la número 1 en el top 10 de OWASP 

¿Qué es una inyección SQL?

En primer lugar, una inyección SQL es un tipo de vulnerabilidad en la que un atacante aprovecha un campo de entrada para ejecutar comandos SQL en el motor de base de datos. Esto es posible ya que los datos no son filtrados ni sanitizados por la aplicación y esta realiza consultas dinámicas a la base de datos no parametrizadas. Para entenderlo mejor pongamos un ejemplo. 
Imaginemos que la aplicación utiliza la siguiente sentencia para obtener nombre y apellido de la cuenta de usuario y este ID se recoge de la URL. 

String query = "SELECT nombre, apellido FROM cuentas WHERE custID='" + request.getParameter("id") + "'";

En un uso normal de la aplicación, el ID será un valor numérico, por lo tanto, la sentencia que se ejecutará en la base de datos será de este tipo: 

SELECT nombre, apellido FROM cuentas WHERE custID=’5’;

¿Qué sucede si en lugar de enviar un número como 5 el usuario modifica este parámetro y envía ‘ or ‘1’=’1? 

SELECT nombre, apellido FROM cuentas WHERE custID=’ ‘ or ‘1’=’1’;

Esta consulta, en lugar de responder con la información de la cuenta del usuario 5, responderá con información de todas las cuentas, ya que la cláusula where es siempre positiva debido a que 1 siempre es igual a 1. 
Con esto podemos ver que se está introduciendo código SQL que es interpretado directamente por el motor de base de datos. El problema es que, con esto, un usuario malintencionado no solo podría obtener los datos de la tabla cuentas, sino que podría obtener información de otras tablas. 
Al poder enviar sentencias que son interpretadas por el motor de base de datos, podría enviarse una consulta algo más compleja que lo que realizará fuera obtener información de otra tabla. Por ejemplo, si el atacante envía como ID: 5‘ UNION SELECT usuario, password  FROM usuarios– – 
La consulta al motor de base de datos sería la siguiente: 

SELECT nombre, apellido FROM cuentas WHERE custID= ’5'UNION SELECT usuario, password FROM usuarios--’;

Esta consulta devolvería el usuario y contraseña de la tabla usuarios. 
De todos modos, la cosa no termina aquí. Estos dos claros ejemplos darían información al atacante siempre y cuando el resultado de esa consulta sea retornado por la aplicación, pero ¿qué sucede si los resultados de esa consulta no son retornados a la aplicación?, ¿Sigue siendo la aplicación vulnerable? 
La respuesta es sí y estaríamos ante una inyección SQL ciega.  
Esta es igual de peligrosa que la ya comentada debido a que un usuario podría eliminar o modificar el contenido, pero, además, aún que de forma menos directa podría obtener datos mediante consultas, por ejemplo, que causen un retraso en la respuesta del servidor.  
Un atacante podría enumerar datos realizando consultas sucesivas del siguiente modo: 

  • Si la primera letra de la contraseña es una “A”, esperar 5 segundos. 
  • Si la primera letra de la contraseña es una “B”, esperar 5 segundos. 

Así con cada letra, si la respuesta tarda cinco segundos es que es la letra por la que preguntamos, si no es así continuamos con la siguiente. Siguiendo esta secuencia se conseguirá obtener el dato completo. 
Si continuamos con el ejemplo podríamos ejecutar la siguiente consulta para ver si la contraseña comienza por 2:  

SELECT nombre, apellido FROM cuentas WHERE custID= ’’UNION SELECT IF(SUBSTRING(password,1,1)=CHAR(50), BENCHMARK(5000000,ENCODE(‘MSG’,'by 5 seconds’)),null) FROM usuarios WHERE id_usuario=1--’;

En esta consulta se utiliza un condicional if que indica que, si el primer carácter de password es igual al char 50 que es 2, la base de datos tiene que ejecutar la operación encode 5000000 veces, ejecución que causará un retraso en la respuesta, si no es igual a 2 no hará nada y no tendremos retraso en la respuesta. De este modo y con consultas sucesivas podremos obtener la contraseña. 
Esta es solo una de las posibilidades, pero existen otras como la extracción de los datos mediante consultas DNS o la enumeración basándose en pequeñas diferencias generadas en la web. 
Con todo lo visto ya nos podemos hacer una idea de la criticidad de la vulnerabilidad, pero esto no es todo. Debido a diversas funcionalidades de los motores de bases de datos, en algunos casos, cuando se pueden ejecutar comandos SQL, también es posible llamar a funciones que permiten ejecutar comandos en el sistema operativo como por ejemplo la función xp_cmdshell en SQL Server. Por lo que esta vulnerabilidad podría permitir a un atacante tomar el control total del servidor. 
Esto que podría parecer complicado y al alcance de solo unos pocos, hoy en día, está completamente automatizado en programas al alcance de cualquier persona con acceso a Internet. 

¿Cómo nos podemos proteger?

Ahora que sabemos que es una inyección SQL veamos cómo podemos evitarla.  
En primer lugar, nunca podemos confiar en los datos introducidos por un usuario. Por lo tanto, si tenemos un campo de tipo id, en el que solo debemos recibir números, debemos verificar antes de utilizar ese valor que efectivamente se trata de un número. Para cualquier otro caso deberíamos realizar lo mismo, utilizar listas blancas que solo permiten los valores esperados. 
Esto no solo aplica para campos visibles en la interfaz de usuario, sino también para cualquier parámetro oculto o cabecera que se envíe al servidor. 
En segundo lugar, no se deben utilizar sentencias SQL construidas de forma dinámica concatenando el texto de la sentencia con variables modificables por el usuario. Debemos llamar a procedimientos almacenados utilizando parámetros. De este modo, las variables enviadas y modificadas por el usuario no serán interpretadas por el motor de base de datos. 
Como buenas prácticas también es recomendable no utilizar usuarios con altos privilegios con el objetivo de evitar que en caso de vulnerabilidad se pueden acceder a datos sensibles o ejecutar comandos en el sistema operativo y por último debemos evitar proporcionar información, como errores, que pueden ayudar a un atacante en la explotación de una vulnerabilidad de este tipo. 

Conclusión

Pese a enfrentarnos a una vulnerabilidad conocida desde hace más de 20 años y la cual tiene una solución efectiva, esta vulnerabilidad sigue apareciendo en múltiples aplicaciones y la categoría inyección donde predomina la inyección SQL sigue la primera en el TOP 10 de OWASP. Esto es realmente preocupante ya que estamos hablando de una vulnerabilidad realmente crítica que puede poner en riesgo datos sensibles y en el peor de los casos puede suponer que se tome el control total del servidor.  Desde A2Secure trabajamos para descubrir este tipo de vulnerabilidades y ayudar a nuestros clientes en su solución. Si crees que tu aplicación podría ser vulnerable, no dudes en contactar con uno de nuestros expertos. 
Autor: Adan Alvarez

Los comentarios están cerrados.