Vulnerabilidades en páginas Web (y 5): SQL Injection
Este artículo es el último de la serie sobre vulnerabilidades en páginas Web. En artículos anteriores hemos visto las vulnerabilidades Command Injection, LFI (Local File Inlcusion) y RFI (Remote File Inclusion), XSS (Cross Site Scripting) y reconocimiento WEB. En el dedicado a la vulnerabilidad XSS, veíamos cómo los atacantes podían inyectar código malicioso usando JavaScript, que es un lenguaje del lado del cliente; por lo tanto, el código se ejecuta en el navegador del usuario. La vulnerabilidad que vamos a ver hoy también es de inyección de código, pero en este caso del lado del servidor, concretamente del servicio de base de datos sobre la que se almacena la información de la página Web. Si un atacante consigue acceso a esta base de datos, puede realizar acciones realmente peligrosas.
Artículo elaborado por José Arroyo Viana, administrador de sistemas y experto en ciberseguridad de Extra Software.
CONTENIDOS
La explotación exitosa de una inyección SQL puede ser devastadora para una organización y es una de las vulnerabilidades de las aplicaciones web más comúnmente explotadas.
¿Qué es SQL Injection?
Ante la necesidad de usar contenido dinámico en las aplicaciones Web actuales, muchas dependen de una base de datos para almacenar datos que serán solicitados y procesados por la aplicación Web. Las aplicaciones Web realizan consultas sobre estas bases de datos para acceder a los datos almacenados en ellas, para realizar estas consultas se utiliza un lenguaje de consultas estructurado o SQL (Structured Query Language).
Para entenderlo mejor vamos a ver un ejemplo. Nos han hecho una página Web para nuestra empresa en la que tenemos una zona privada que para acceder es necesario introducir un nombre de usuario y una contraseña, esta página Web almacena los datos de inicio de sesión en una base de datos llamada “Empresa”.
La aplicación Web hará una consulta para extraer los datos de la base de datos para mostrarlos. Para realizar esta consulta se utilizará la instrucción SELECT. Con ella, una vez localizada la base de datos y tabla que le interesa, puede filtrar los datos para que muestre algunos registros, por ejemplo, los registros de la tabla Usuarios donde la columna id sea igual a 1. Para ello se usará también la cláusula WHERE. Así quedaría la instrucción:
- SELECT * from Usuarios WHERE id = 1;
Ahora que sabemos lo que es una consulta SQL vamos a ver como los atacantes utilizan SQL injection.
¿Cómo funciona SQL Injection?
Un ataque de Inyección SQL se produce cuando un valor en la solicitud del cliente se utiliza dentro de una consulta de SQL sin un saneamiento previo. Si como desarrolladores Web no hemos saneado el código y confiamos en los datos proporcionados por los usuarios, los atacantes pueden extraer información oculta de las bases de datos o tomar el control del servidor.
Por ejemplo, si la consulta anterior donde estábamos consultando el registro con el id 1 se realiza en una página Web para mostrar los datos de los usuarios, le indicamos que queremos que nos ordene la salida de los datos por la columna número 10.
Como vemos nos indica que la columna 10 es desconocida. La columna 10 es evidente que no existe ya que solo tenemos 4, pero a los atacantes les interesa saber cuál es el número de columnas que hay en tabla. Si lo ordenamos por 4 ya se nos muestran los datos:
El atacante una vez que sabe el número de columnas que tiene la tabla a la que estamos consultando va a hacer una consulta de 4 columnas uniéndola a la consulta actual mediante la cláusula UNION:
SELECT * from Usuarios where id = 1 UNION SELECT 1,2,3,4;– -;
Ahora por cada columna se nos ha agregado una etiqueta numérica. Nosotros como atacantes nos podremos aprovechar de estas etiquetas para poner sentencias de SQL y que nos las muestre. Por ejemplo, con database() sabremos qué base de datos está en uso por el servicio Web
SELECT * from Usuarios WHERE id = 1 UNION SELECT 1,database(),3,4;– -;
Mediante la unión de consultas hemos conseguido saber cuál es la base de datos que está en uso, mediante esta técnica podríamos extraer toda la información de todas las bases de datos alojadas en el servidor.
Ejemplo de SQL Injection
Este tipo de Inyección SQL la podemos considerar como Error-Based (basada en error). Nos vamos a aprovechar del propio error que se nos va a mostrar en la página Web para listar información privilegiada de la base de datos.
Tenemos un cuadro de texto de una página web en el que escribimos nuestro nombre de usuario y contraseña y al ejecutarlo nos aparecen los detalles de nuestra cuenta en la página. Para ello nos hemos creado antes una cuenta en esa aplicación web. El nombre de usuario es ‘extra’ y la contraseña ‘password123’
Vemos que la dirección Web o URL que se nos genera es la siguiente:
http://10.17.20.71/mutillidae/index.php?page=userinfo.php&username=extra&password=password123&user-info-php-submitbutton=View+Account+Details
Nosotros como atacantes podemos intuir que los parámetros username y password (que hemos destacado en negrita) forman parte del SELECT de la consulta que se le está haciendo a la base de datos. Como no sabemos el número de columnas que tiene y es necesario para usar la unión de consultas, vamos a ordenar por la columna número 10. Al reproducir la URL, vamos a destacar en rojo la parte del código que está inyectado, para mayor claridad. (Al final del código inyectado, hemos puesto un comentario de línea usando los siguientes caracteres (– -). Esto se utiliza para que el código inyectado no aplique a lo que va a continuación).
http://10.17.20.71/mutillidae/index.php?page=user-info.php&username=extra’ order by 10– –&password=password123&user-info-php-submitbutton=View+Account+Details
Vemos que nos aparece el mismo error que por consola donde nos indica que no existe esta columna y no puede ordenar por ella. Le hacemos la misma consulta, pero ordenando por 5 columnas y ya no nos devuelve el error por lo que sabemos que hay 5 columnas:
17.20.71/mutillidae/index.php?page=user-info.php&username=extra’order by 5– – &password=password123&user-info-php-submit-button=View+Account+Details
Ahora hacemos una unión con 5 columnas y vemos que nos las muestra poniendo los números debajo, en este caso las columnas visibles son la 2,3 y 4:
http://10.17.20.71/mutillidae/index.php?page=user-info.php&username=extra’ union select 1,2,3,4,5– –&password=password123&user-info-php-submitbutton=View+Account+Details
Ahora sabemos que si ponemos una función de SQL en los espacios donde tenemos los números 2,3 o 4 nos aparecerá por pantalla el resultado de esa función o instrucción. Para llevar a cabo el ataque, donde pone el 2 le ponemos la sentencia database() nos mostrará la base de datos que está actualmente en uso que es owasp10.
http://10.17.20.71/mutillidae/index.php?page=user-info.php&username=extra’ union select 1,database(),3,4,5– –&password=password123&user-info-php-submitbutton=View+Account+Details
De la misma forma podríamos poner la sentencia user() para que nos muestre el usuario que está corriendo el servicio:
También podríamos hacer más cosas como leer ficheros locales del equipo, extraer toda la información de la base de datos e incluso modificarla.
Solución
Muchas veces se intentan prevenir este tipo de vulnerabilidades usando filtros. Los filtros pueden hacer que parezca que no hay vulnerabilidades, pero son algo que el atacante puede saltarse.
Algunos programadores también usan una lista negra, por ejemplo, prohíben el uso de UNION, INSERT o sentencias del estilo. De nuevo esto no es 100% efectivo. Otros usan una lista blanca en vez de negra, ….
La mejor manera de evitar SQL Injection es programar la aplicación Web de manera que no permita hacerlas. Una forma sería crear una declaración parametrizada donde los datos y el código estén separados. Luego podemos usar los filtros como segunda línea de defensa. Y también es recomendable usar los menos privilegios posibles para el usuario y usar un usuario por cada base de datos.
Conclusión
En el artículo hemos visto la vulnerabilidad SQL Injection. Como hemos explicado, esta vulnerabilidad se aprovecha de las peticiones que hace la página o aplicación Web a su backend de base de datos.
En el ejemplo hemos visto una vulnerabilidad basada en error, pero hay otros tipos como las basadas en tiempo, en este caso la página Web no devuelve ningún error y se utilizan condiciones basadas en tiempo.
Como siempre, os hemos dejado finalmente unas pautas de seguridad mínimas que se deben seguir para evitar ser víctima de este tipo de vulnerabilidades.