¿Qué es, cómo se hace y cómo se evitan las Inyecciones SQL?

0
1329
Imagen:sqldumper.jpg

Un ataque de inyección SQL consiste en la inserción o la “inyección” de una consulta SQL a través de los datos de entrada de la aplicación del cliente. El éxito de explotar una inyección SQL puede ser el de leer datos sensibles de la base de datos, modificar la base de datos para (Insertar / Actualizar / Borrar), ejecutar operaciones de administración de la base de datos (por ejemplo parar el DBMS), recuperar el contenido de un archivo presente en el sistema de ficheros y/o DBMS, Y hasta en algunos casos, llegar a la shell (línea) de comandos del sistema operativo.

Descripción de la problemática.

Los ataques de inyección SQL se pueden dividir en las tres clases siguientes:

  • Inband: los datos se extraen usando el mismo canal que se utiliza para inyectar el código del SQL. Ésta es la clase más directa del ataque, en la cual los datos recuperados se presentan directamente en la página web de la aplicación.
  • Fuera de banda: los datos se recuperan usando un diverso canal (e.g., un email con los resultados de la pregunta que se genera y se envían al tester).
  • Deductivo: no hay transferencia real de datos, pero el tester puede reconstruir la información enviando peticiones particulares y observando el comportamiento resultante del servidor de la DB.

Con independiente de la clase del ataque, un ataque acertado de inyección SQL requiere al atacante hacer una pregunta sintácticamente correcta en SQL. Si la aplicación devuelve un mensaje de error generado por una pregunta incorrecta, será más fácil reconstruir la lógica de la pregunta original y, por lo tanto, entender cómo realizar la inyección correctamente. Sin embargo, si la solicitud esconde los detalles del error, a continuación, el tester debe ser capaz de utilizar técnicas de ingeniería inversa de la lógica de la consulta original. El último caso se conoce como “inyección oculta del SQL”.

Prueba y ejemplo de la caja negra

Detección de inyección del SQL

El primer paso en esta prueba es entender cuando nuestra aplicación conectada con un servidor de DB para tener acceso a unos ciertos datos. Los ejemplos típicos de casos cuando una aplicación necesita hablar con un DB incluyen:

  • Formas de la autentificación: cuando la autentificación se realiza usando un formulario web, en ocasiones las credenciales del usuario están comprobadas contra una base de datos que contenga todos los nombres del usuario y contraseñas (o, mejor, los hashes de las contraseñas)
  • Motores de la búsqueda: las cadenas insertadas por el usuario se podrían utilizar en una pregunta del SQL que extrae todos los registros relevantes de una base de datos.
  • Sitios de comercio electrónico: los productos y sus características (precio, descripción, disponibilidad,…) será muy probable que sean almacenados en una base de datos relacional.

El tester tiene que hacer una lista de todos los campos de entrada cuyos valores podrían ser utilizados en la elaboración de una consulta SQL, incluidos los campos ocultos de peticiones POST y, a continuación, prueba de ellos por separado, tratando de interferir con la consulta y generar un error. La primera prueba por lo general consiste en añadir una comilla simple ( ‘) o un punto y coma (;) sobre el terreno sometido a la prueba. La primera se utiliza en SQL como una cadena de terminación y, de no ser filtrada por la aplicación, daría lugar a una pregunta incorrecta. El segundo se utiliza para poner fin a una instrucción SQL y, en caso de que no se filtra, también es probable que genere un error. La salida de una vulnerables sobre el terreno podría parecerse a la siguiente (en un Microsoft SQL Server, en este caso):

Microsoft OLE DB Provider for ODBC Drivers error '80040e14'

[Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before the

character string ''.

/target/target.asp, line 113

También comenta (–) y otras palabras claves del SQL como “Y” y “O” puede ser utilizado para intentar modificar la pregunta. Un muy simple pero técnica eficaz sigue siendo a veces simplemente insertar una secuencia donde se espera un número, como un error como el siguiente pudo ser generado:

Microsoft OLE DB Provider for ODBC Drivers error '80040e07'

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the

varchar value 'test' to a column of data type int.

/target/target.asp, line 113

Un mensaje de error completo como los que está en los ejemplos proporciona una abundancia de la información al tester para montar una inyección acertada. Sin embargo, los usos no proporcionan a menudo tanto el detalle: “un error de servidor simple 500” o una página de encargo del error se pudo publicar, significando que necesitamos utilizar técnicas ocultas de la inyección. En todo caso, es muy importante probar el separately* del campo del *each: solamente una variable debe variar mientras que todo el otra sigue siendo constante, para entender exacto qué parámetros son vulnerables y cuáles no son.

Prueba estándar de la inyección del SQL

Considere la pregunta siguiente del SQL:

SELECT * FROM Users WHERE Username='$username' AND Password='$password' 


Una pregunta similar se
utiliza generalmente de la aplicación web para authenticar a un
usuario. Si la pregunta vuelve un valor que significa que dentro de la
base de datos un usuario con ése las credenciales existe, después se
permite al usuario abrirse una sesión al sistema, si no el acceso se
niega. Los valores de los campos de la entrada se obtienen generalmente
del usuario a través de una forma de la tela. Suponga que insertamos
los valores siguientes del username y de contraseña:

$username = 1' or '1' = '1

$password = 1' or '1' = '1

La pregunta será:

SELECT * FROM Users WHERE Username='1' OR '1' = '1' AND Password='1' OR '1' = '1' 

Si suponemos que los valores de los parámetros están enviados al servidor con el método del CONSEGUIR, y si el dominio del Web site vulnerable es www.example.com, es la petición que realizaremos:

http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1&password=1'%20or%20'1'%20=%20'1 

Después de que un análisis corto nosotros note que la pregunta vuelve un valor (o un sistema de valores) porque la condición es siempre verdad (O 1=1). De esta manera el sistema ha authenticado al usuario sin saber el username y la contraseña.
En algunos sistemas la primera fila de una tabla de usuario sería un usuario del administrador. Éste puede ser el perfil vuelto en algunos casos. Otro ejemplo de la pregunta es el siguiente:

SELECT * FROM Users WHERE ((Username='$username') AND (Password=MD5('$password'))) 

En este caso, hay dos problemas, uno debido al uso de paréntesis y uno debido al uso de la función de picadillo MD5. En primer lugar, resolvemos el problema de paréntesis. Eso consiste en simplemente el agregar de un número de paréntesis cerrados hasta que obtengamos una pregunta corregida. Para resolver el segundo problema, intentamos invalidar la segunda condición. Agregamos a nuestra pregunta un símbolo final que signifique que un comentario está comenzando. De esta manera, todo que sigue tal símbolo se considera un comentario. Cada DBMS tiene sus propios símbolos del comentario, sin embargo, un símbolo común a la parte más mayor de la base de datos es/*. En Oracle el símbolo es “–”. Este haber dicho, los valores que utilizaremos como son el username y la contraseña:

$username = 1 ' o “1” = “1”))/*

$password = foo

De esta manera, conseguiremos la pregunta siguiente:

SELECT * FROM Users WHERE ((Username='1' or '1' = '1'))/*') AND (Password=MD5('$password'))) 

La petición del URL será:

http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'))/*&password=foo 

Cuál vuelve un número de valores. A veces, el código de la autentificación verifica que el número de tuple vuelto sea exactamente igual a 1. En los ejemplos anteriores, esta situación sería difícil (en la base de datos hay solamente un valor por usuario). Para circundar este problema, es bastante para insertar un comando de SQL que imponga la condición que el número del tuple vuelto debe ser uno. (Uno registra vuelto) para alcanzar esta meta, utilizamos al operador “ del LÍMITE”, donde está el número el de los tuples que esperamos que sean vueltos. Con respecto al ejemplo anterior, el valor de los campos username y la contraseña serán modificados como sigue:

$username = 1 ' o “1” = “1”)) LÍMITE 1/* 

$password = foo

De esta manera, creamos una petición como el siguiente:

http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'))%20LIMIT%201/*&password=foo 

Prueba de la inyección del SQL de la pregunta de la unión

Otra prueba implica el uso del operador de la UNIÓN. Utilizan a este operador en inyecciones del SQL para ensamblar una pregunta, forjada adrede por el tester, a la pregunta original. El resultado de la pregunta forjada será ensamblado al resultado de la pregunta original, permitiendo que el tester obtenga los valores de los campos de otras tablas. Suponemos por nuestros ejemplos que la pregunta ejecutada del servidor es la siguiente:

SELECT Name, Phone, Address FROM Users WHERE Id=$id 

Fijaremos el valor siguiente de la identificación:

$id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCarTable

Tendremos la pregunta siguiente:

SELECT Name, Phone, Address FROM Users WHERE Id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCarTable 

El cuál ensamblará el resultado de la pregunta original con todos los usuarios de la tarjeta de crédito. La palabra clave ALL es necesaria conseguir alrededor de las preguntas que utilizan la palabra clave DISTINTA. Por otra parte, notamos que más allá de los números de tarjeta de crédito, hemos seleccionado otros dos valores. Estos dos valores son necesarios, porque la pregunta dos debe tener un número igual de parámetros, para evitar un error de sintaxis.

Prueba oculta de la inyección del SQL

Hemos precisado que hay otra categoría de inyección del SQL, llamado la inyección Blind SQL, en la cual no se sabe nada en el resultado de una operación. Por ejemplo, este comportamiento sucede en caso de que el programador haya creado una página de encargo del error que no revela cualquier cosa en la estructura de la pregunta o en la base de datos. (La página no vuelve un error del SQL, él puede apenas volver un HTTP 500).
Usando los métodos de la inferencia, es posible evitar este obstáculo y tener éxito así para recuperar los valores de algunos campos deseados. Este método consiste en el realizar de una serie de preguntas boleanas al servidor, el observar de las respuestas y finalmente el deducir del significado de tales las respuestas. Consideramos, como siempre, el dominio de www.example.com y nosotros suponemos que contiene un parámetro nombrado identificación vulnerable a la inyección del SQL. Esto significa eso que realiza la petición siguiente:

http://www.example.com/index.php?id=1' 

conseguiremos una página con un error de encargo del mensaje que sea debido a un error sintáctico en la pregunta. Suponemos que es la pregunta ejecutada en el servidor:

SELECT field1, field2, field3 FROM Users WHERE Id='$Id' 

cuál es explotable con los métodos vistos previamente. Qué queremos obtener es los valores del campo del username. Las pruebas que ejecutaremos permitirán que obtengamos el valor del campo del username, extrayendo tal carácter del valor por el carácter. Esto es posible con el uso de algunas funciones estándar, presente prácticamente en cada base de datos. Por nuestros ejemplos, utilizaremos las pseudo-funciones siguientes:

SUBSTRING (texto, comienzo, longitud): vuelve una SUBSTRING a partir de la posición “comienzo” del texto y de la longitud “longitud”. Si el “comienzo” es mayor que la longitud del texto, la función vuelve un valor nulo.

ASCII (caracter): Devuelve el valor del ASCII del carácter de la entrada. Si devuelve un valor nulo caracter es 0.

LENGTH (texto): Devuelve la longitud en los caracteres del texto de entrada.

Con tales funciones, ejecutaremos nuestras pruebas en el primer carácter y, cuando hubiéramos descubierto el valor, pasaremos al segundo y así sucesivamente, hasta que hayamos descubierto el valor entero. Las pruebas se aprovecharán de la función SUBSTRING, para seleccionar solamente un carácter a la vez (la selección de un solo carácter significa imponer el parámetro de 1 a la longitud), y la función ASCII, para obtener el valor del ASCII, de modo que poder hacer la comparación numérica. Los resultados de la comparación serán hechos con todos los valores de la tabla del ASCII, hasta que se encuentre el valor correcto. Como ejemplo, utilizaremos el valor siguiente para la identificación:

$Id=1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1 

que crea la pregunta siguiente (de ahora en adelante, nosotros la llamará “pregunta deductiva”):

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1'

El ejemplo anterior vuelve un resultado si y solamente si el primer carácter del username del campo es igual al valor 97 del ASCII. Si conseguimos un valor falso, después aumentamos el índice de la tabla del ASCII a partir del 97 a 98 y repetimos la petición. Si en lugar de otro obtenemos un valor verdadero, fijamos a cero el índice de la tabla del ASCII y analizamos el carácter siguiente, modificando los parámetros de la función SUBSTRING. El problema es entender de qué manera podemos distinguir las pruebas que vuelven un valor verdadero de las que vuelvan falso. Para hacer esto, creamos una pregunta que vuelva siempre falso. Esto es posible usando el valor siguiente para la identificación:

$Id=1' AND '1' = '2 

por cuál creará la pregunta siguiente:

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND '1' = '2' 

La respuesta obtenida del servidor (que es código HTML) será el valor falso para nuestras pruebas. Ésto es suficiente para verificar si el valor obtenido de la ejecución de la pregunta deductiva es igual al valor obtenido con la prueba ejecutada anteriormente. A veces, este método no funciona. En caso de que el servidor devuelva dos páginas como resultado de dos peticiones consecutivas idénticas de la web, no podremos discriminar el valor verdadero del valor falso. En estos casos particulares, es necesario utilizar filtros particulares que permiten que eliminemos el código que los cambios entre las dos peticiones y obtener una plantilla. Porque de cada petición deductiva ejecutada, extraeremos la plantilla relativa de la respuesta usando la misma función, y realizaremos un control entre las dos plantillas para decidir el resultado de la prueba.

En el anterior debate, no hemos tratado el problema de la determinación de la condición de terminación de las pruebas, es decir, cuando hay que poner fin al procedimiento de inferencia. Una de las técnicas que se utiliza para ello es una característica de la función SUBSTRING y de la función LENGHT. Cuando la prueba se compara el carácter actual con el código ASCII 0 (es decir, el valor null) y la prueba devuelve el valor true ‘cuando está presente’, entonces o bien se realizan con la inferencia procedue (hemos escaneado toda la cadena), o el valor que han analizado contiene el carácter nulo.

Insertaremos el valor siguiente para la identificación del campo:

$Id=1' AND LENGTH(username)=N AND '1' = '1 

Donde N es el número de caracteres que hemos analizado hasta ahora (no contando con el valor nulo). La pregunta será:

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND LENGTH(username)=N AND '1' = '1'

La consulta devuelve true o false. Si obtenemos cierto, entonces, hemos completado la inferencia y, por lo tanto, sabemos el valor del parámetro. Si obtenemos falso, esto significa que el carácter nulo está presente en el valor del parámetro, y tenemos que seguir para analizar el siguiente parámetro hasta que encontremos otro valor nulo.

El ataque oculto de inyección SQL necesita grandes cantidades de consultas. El tester puede necesitar una herramienta automática para explotar la vulnerabilidad. Una herramienta simple, vía la cual realizar esta tarea, CONSIGUEN peticiones en el DB de MySql, es SqlDumper, que se demuestra abajo.

Imagen:sqldumper.jpg

Inyección del procedimiento almacenado

Pregunta: ¿Cómo se puede eliminar el riesgo de inyección SQL?
Respuesta: Procedimientos almacenados.
He visto esta respuesta demasiadas veces sin cualificaciones. El uso de procedimientos de almacenaje (para Bases de datos Relacionales) no asiste simplemente a la mitigación de la inyección del SQL. Si no dirigido correctamente, el SQL dinámico dentro de procedimientos almacenados, éste puede ser tan vulnerable a la inyección del SQL como el SQL dinámico dentro de una Web.

Al usar el SQL dinámico dentro de un procedimiento almacenado, la aplicación debe limpiar correctamente la entrada del usuario para eliminar el riesgo de inyección del código. Si no se limpia la consulta, el usuario podría incorporar un SQL malévolo que será ejecutado dentro del procedimiento almacenado.

La prueba ‘de caja negra’ utilizada para la inyección SQL para comprometer el sistema.
Considere el procedimiento almacenado del servidor siguiente del SQL:

Create procedure user_login @username varchar(20), @passwd varchar(20) As

Declare @sqlstring varchar(250)

Set @sqlstring = ‘

Select 1 from users

Where username = ‘ + @username + ‘ and passwd = ‘ + @passwd

exec(@sqlstring)

Go

El Usuario introduce:

anyusername or 1=1′ anypassword

Este procedimiento no esteriliza la entrada por lo tanto permite que el valor de vuelta demuestre un registro existente con estos parámetros.

NOTA: Este ejemplo puede parecer inverosímil debido al uso del SQL dinámico de abrir una sesión a un usuario sino de considerar una pregunta dinámica de la información donde el usuario selecciona las columnas para ver. El usuario podría insertar código malévolo en este panorama y comprometer los datos.
Considere el procedimiento almacenado del servidor siguiente del SQL:

Create procedure get_report @columnamelist varchar(7900) As

Declare @sqlstring varchar(8000)

Set @sqlstring = ‘

Select ‘ + @columnamelist + ‘ from ReportTable‘

exec(@sqlstring)

Go

Usuario introduce:

1 from users; update users set password = 'password'; select *

Esto dará lugar al funcionamiento del informe y a todas las contraseñas de usuarios que son actualizados.

Artículos Relacionados.

Technology specific Testing Guide pages have been created for the following DBMSs:

Las Herramientas tecnológicas específicas para las Pruebas de ésta Guía se han extraido de las páginas que han creado los siguientes DBMS:

Referencias.

Whitepapers

Herramientas.

LEAVE A REPLY

Please enter your comment!
Please enter your name here