Mostrando entradas con la etiqueta Access. Mostrar todas las entradas
Mostrando entradas con la etiqueta Access. Mostrar todas las entradas

lunes, junio 09, 2014

Explotar SQL Injection en Access para atacar Windows

Cuando en una auditoría de seguridad te toca lidiar con una vulnerabilidad SQL Injection en la que la base de datos está almacenada en un motor JET de Microsoft Access las herramientas con las que hay que trabajar son muy distintas a las del resto de los motores de bases de datos. No es que cuando te enfrentes a un Microsoft SQL Server, un Oracle Database o un MySQL sean las cosas exactamente iguales en ellos, pero la ausencia de un diccionario de datos que consultar como los esquemas de Oracle o la no existencia de procedimientos almacenados como los que hay en Transact-SQL hacen las cosas siempre un poco más difíciles.


Figura 1: Codemotion 2013 - Feliz 15 aniversario SQL Injection

En la charla que di en el pasado Codemotion que os dejo por aquí hago un repaso a las principales técnicas de SQL Injection durante los últimos 15 años, y si quieres aprender bien las técnicas de SQL Injection puedes leer el libro de Hacking de Aplicaciones Web: SQL Injection.

SQL Injection en Microsoft Access

En uno de esos sitios me topé con SQL Injection in-band en el que era posible construir una consulta de inyección con un UNION. Hacer esto había sido fácil porque el truco para hacerlo es sencillo y lo había utilizado ya para hacer las consultas pesadas en el trabajo de Time-Based Blind SQL Injection using Heavy Queries sobre Access. Solo debía tirar sobre una de las tablas que existen en las bases de datos y que pueden utilizarse en una consulta SQL

En este caso la tabla que utilicé fue MSysAccessObjects que está disponible en las versiones de Microsoft Access 97 o Microsoft Access 2000, ya que en 2003 y 2007 esa tabla fue cambiada por otras. La lista de tablas que hay en cada versión de MS Access 97 a MS Acces 2007 - a partir de MS Access 2010 el formato dejó de ser mdb - está disponible en muchos sitios. Yo tiré de este tutorial de Net_Spy. Como se puede ver, aunque cada versión tiene una lista de tablas comunes, solo son utilizables en una consulta SQL aquellas que tienen el carácter asterisco a su lado.

Figura 2: Tablas disponibles en las versiones de Microsoft Access 97 a 2007

Una vez que está construida la consulta de inyección SQLi con el UNION usando constantes y apoyándonos en una tabla conocida como MSysAccessObjects, el siguiente paso es descubrir las tablas que forman parte de esa base de datos, para lo que se debe tirar a los sospechosos habituales - como users, users, customers, clients, usuarios, etcétera -.

En mi caso, no hubo suerte con los sospechosos habituales, así que había que pasar a tirar un diccionario con una buena cantidad de palabras porque no había encontrado ninguna. Esto puede pasar muchas veces. Si el dueño de la base de datos ha decidido utilizar una nomenclatura del tipo tablaAppCompras_usuarios, te puedes hacer anciano esperando a descubrir una de esas tablas. Ese es el problema de no tener un diccionario de datos que se pueda consultar en el motor. 

Si se hubiera descubierto el nombre de las tablas, luego se puede abusar de los mensajes de error que se obtienen al usar la claúsula Group BY, o bien volver hacer un ataque de diccionario otra vez contra los campos de las tablas. En este caso en concreto solo fui capaz de sacar el nombre de unos campos de la tabla con ataques out-band generando mensajes de error del motor JET.

No obstante, en Microsoft Access se pueden hacer cosas muy chulas fuera del motor de la base de datos, que paso a detallaros, ya que os pueden venir bien en una auditoría en el futuro. Todos ellos son ataques out-band que se apoyan en el mensaje de error del motor JET de Microsoft Access.

Descubrir si un fichero existe y/o si está en uso

En el lenguaje SQL de Microsoft Access se puede hacer uso del operador IN para especificar que una tabla se encuentra dentro de otro fichero mdb de Microsoft Access. Es decir, es posible hacer una consulta que haga JOIN entre dos tablas y que cada una de ellas se encuentre en un fichero .mdb distinto. La consulta es tan sencilla como:
and exists (Select 1,2,3 from MSysAccessObjects IN 'autoexec.bat')
Poniendo el nombre de cualquier fichero - no importa si el fichero no es de bases de datos Microsoft Access - recibiremos un error distinto en tres situaciones posibles:

a) El fichero no existe

Figura 3: error al usar "and exists (Select 1 from MSysAccessObjects in 'foca.foca')

b) El fichero existe pero está en uso y no se puede abrir

Figura 4: El fichero boot.ini existe pero no se puede abrir

c) El fichero existe, se puede abrir, pero no es una base de datos Access

Figura 5: error al usar "and exists (Select 1 from MSysAccessObjects in 'autoexec.bat')

Por desgracia, yo me he encontrado con algunas limitaciones con estas inyecciones, que paso a detallaros:
1.- No es posible cambiar de unidad, así que si la base de datos del motor JET está en la unidad C:, este truco te permitirá listar solo ficheros de esa unidad. 
2.- No se pueden listar ficheros sin extensión. El error que he obtenido ha sido siempre el mismo con ficheros que sé que existen y con ficheros que se que no existen. 
3.- No funciona para listar directorios. Si se intenta usar para saber si un directorio existe o no, solo se puede hacer con algún fichero que se conozca que está dentro. Si no, el mensaje de error que se obtiene es siempre el mismo. 
4.- No se pueden utilizar comodines * o ? para escribir el nombre de los ficheros. 
5.- No se pueden utilizar las variables de entorno tipo %USERDIR%, %WINDIR%, etcétera.
Sacar los datos de un fichero mdb en el mismo servidor

En los ejemplos anteriores se ha supuesto que el objetivo era conocer la existencia o no de un fichero, pero si se descubriera otro fichero mdb de otra aplicación en el sistema. Se podría extraer toda la información de la misma usando como base la conectada a la aplicación con SQL Injection que se está utilizando para lanzar el ataque.

Figura 6: Extraer datos de otra base de datos mdb no expuesta a Internet

Sacar la lista de usuarios de un sistema Windows

Aprovechándome de esta función de listar ficheros, decidí que un buen truco sería listar ficheros de los perfiles de los usuarios que existieran. En este caso, se puede ver que tirando del directorio de Default User es cómodo construir alguna consulta con algún fichero de los perfiles de usuario.

Figura 7: Ruta al perfil de Default User

En este ejemplo usé NTUSer.dat para tener un fichero sobre el que apoyarme y luego ir cambiando el nombre de los usuarios. Como puede verse, el usuario Administrator existe.

Figura 8: Ruta al perfil del Administrador. Existe y está en uso, lo que significa que la sesión está abierta.

El resto es lanzar un diccionario con los nombres de usuario para tener la lista completa y, por supuesto, aprovecharse de los nombres de usuarios que FOCA hubiera descubierto de los metadatos de los documentos del sitio web.

Descubrir si un fichero existe y/o si está en uso usando formato de nombres 8:3

Acordándome del bug de IIS Shortname se me ocurrió que podría ser buena idea intentar localizar nombres de ficheros de más de 8 caracteres usando la codificación 8:3. Esto haría que fuera bastante más reducido el tiempo con un intento de descubrir ficheros de una carpeta, sobre todo si se va a hacer un ataque de fuerza bruta.

Figura 9: Ruta al perfil de Default User con nombres 8:3

Para comprobarlo, utilicé de nuevo el fichero de perfil de Default User y como se puede ver es posible, por lo que para hacer un ataque de fuerza bruta a la lista de usuarios se simplificaría un poco. Se podría haber hecho de igual forma con el usuario Admini~1, y volveríamos a ver que el fichero está en uso.

Figura 10: Ruta al perfil del Administrador en nombres 8:3

No solo ayuda a reducir el número de ataques, sino a reducir el tamaño de la URL inyectada, lo que siempre viene bien para evitar cualquier límite de tamaño que pudiera existir.

Listar el software instalado en el servidor

Con la misma idea de reconocer usuarios del sistema por los ficheros de su perfil, se podría hacer un listado del software instalado en el equipo - y el que está en arrancado por los errores de acceso en uso - buscando los ficheros de los programas. Para ello habría que tener una lista de rutas de instalación por defecto de los programas más comunes y buscarlos.

Figura 11: Ruta de instalación por defecto de WinZip. No está en el sistema

Si los programas están instalados en otra unidad o están en rutas distintas esto no valdría, pero si se conoce el software se podría intentar preparar, por ejemplo un ataque de evil grade o un exploiting concreto conociendo la existencia de un software vulnerable sin actualizar.

Descubrir la ruta de instalación del servidor web

Otro de las fugas de información que se pueden aprovechar con los errores JET es el del path de instalación del motor en el servicio web. No es la ruta en la que se encuentra el fichero mdb de la base de datos Microsoft Access, pero sí que da información más que suficiente para conocer con qué servidor Windows se está lidiando.

Figura 12: Local path disclosure al buscar en foca.foca

Para conseguirlo solo hay que hacer una consulta SQL inyectada similar a esta:
and exists (Select 1,2,3 from fake_database.fake_table) 
Al no existir el fichero fake_database con una base de datos Microsoft Access en la carpeta donde se encuentra la base de datos actual, se produce un error que muestra la ruta local al servicio IIS.

Listar los roles del servidor. ¿Es un Controlador de Dominio?

Los archivos que tiene un servidor Windows sirven también para identificar los roles que tiene un servidor Windows asignado. Si tiene un servicio DHCP, un servicio DNS o si es un controlador de domino.

Figura 13: Petición de la base de datos de Active Directory. No está disponible.

Por ejemplo, para saber si es un Active Directory se podría buscar la base de datos NTDIS.Dat en la ruta en la que se configura. En este caso no tiene asignado este rol.

Descubrir el nombre del fichero de la base de datos

Uno de los ataques más fáciles de realizar a sistemas que usan como repositorio de datos un fichero mdb de Microsoft Access, es descargar el fichero completo. Para ello hay que averiguar dos cosas que son, el directorio donde se almacena - que deberá ser público para poder descargarlo- y el nombre del fichero.

Si esto se hace en dos fases en lugar de en una, el proceso es un poco más sencillo. Averiguar el nombre del fichero, sin importar el directorio en el que esté, se puede hacer haciendo uso de una consulta inyectada similar a la anterior, pero en lugar de usar una fake_database, usar uno de los nombres posibles:
and exists (Select 1,2,3 from fichero.tabla_existente)
esto permite que se obtenga un error cuando el fichero no exista se obtenga un error que indica que ese fichero mdb no está allí.  Si existe, se conseguirá otra respuesta de la base de datos, en este caso un mensaje de syntax error porque la inyección se ha hecho para forzar el error out-band.

Figura 14: El fichero se llama database.mdb (nada imaginativo)

Una vez conocido el nombre de la base de datos, conocer su ubicación puede realizarse probando en todos los directorios de la web descubiertos con todas las técnicas que ya os dejé en el artículo: Técnicas para descubrir los ficheros de un servidor web.

Una frase para terminar

Un bug de SQL Injection es un fallo muy serio, y si tenemos una base de datos MS Access por medio se pueden hacer muchas cosas en el sistema operativo aunque no seamos capaces de descubrir las tablas de la base de datos. En este caso se ha hecho aprovechando un ataque out-band para ver los mensajes de error de que el fichero existe, la tabla existe, etcétera y errores de sintaxis, pero también se podría haber usado la técnica de Blind SQL Injection o Time-Based Blind SQL Injection using Heavy Queries para generar retardos de tiempo cuando la respuesta fuera correcta.

Saludos Malignos!

miércoles, agosto 27, 2008

Reto Hacking I con Marathon Tool

Hola a tod@s,

Allá por finales del 2006, cuando salió el Reto Hacking I, este fue diseñado para ser pasado utilizando Blind SQL Injection en base a patrones, es decir. Bastaba con fijarse en los cambios de los valores de las pistas cuando se inyecta algo True y cuando se inyecta algo False:

Verdadero: Sale la pista 2

http://www.informatica64.com/retohacking/pista.aspx?id_pista=2 and 1=1

Falso: Sale la pista 1 que es la de por defecto ante ausencia de valores en la query.

http://www.informatica64.com/retohacking/pista.aspx?id_pista=2 and 1=2

La dificultad dos era adivinar que estaba programado usando Access y claro, al no haber diccionario de datos había que adivinar que la había una tabla llamada Contrasena, con una columna llamada Contrasena. Pero... para eso estaban las pistas. Si quieres puedes leer el solucionario que hizo Dani Kachakil sobre él.

Pero... ¿se podría haber sacado sólo con la pista 1, es decir, sin que hubiera existido la pista 2?

http://www.informatica64.com/retohacking/pista.aspx?id_pista=1 and 1=1

http://www.informatica64.com/retohacking/pista.aspx?id_pista=1 and 1=2

Como se puede ver en este caso no hay diferencia pues en caso de error el programa devuelve la pista por defecto, es decir, la pista 1. ¿Solución? Pues pasarlo con consultas pesadas... y Marathon Tool.

Paso 1: Descubrir la tabla y la columna

Verdadero: La consulta dura como unos 6 segundos más o menos

http://www.informatica64.com/retohacking/pista.aspx?id_pista=1 and (select count(*) from MSysAccessObjects as t1, 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 Contrasena from Contrasena)

Falso: La consulta dura poco más de 1 segundo, sólo la latencia de red.

http://www.informatica64.com/retohacking/pista.aspx?id_pista=1 and (select count(*) from MSysAccessObjects as t1, 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 NOT exists (select Contrasena from Contrasena)

Fase 2: Sacar la Contraseña con Marathon Tool

Para esta fase usamos Marathon Tool, se configura como se ve en la imagen. En este caso es un Access 2000, pero podría haber sido un Access 2003/2007 pero esto ya lo tendrías que haber probado en la Fase 1. Después se inicializa la tool

Configuración Access 2000 para Reto Hacking I

Después se debe configurar la tabla y la columna a extraer. Esto se debe realizar en las bases de datos Access porque no disponen de un diccionario de datos, por eso era necesaria la Fase 1:

Configuración de Tabla y columna

Una vez configurada se da al botón de Get Data y Marathon Tool se pone a currar...

Marathon Tool trabajando

En el log se puede apreciar que Marathon Tool ha encontrado el número de registros y la longitud del dato. En este caso 1 registro de 32 caracteres de longitud. Al final, los datos aparecerán en la pestaña de Database data. Una vez aparezca el Hash el resto es crackearlo y listo.

Si te apetece aprender más sobre este tema:

- Time-Based Blind SQL Injection using Heavy Queries (Parte I de II)
- Time-Based Blind SQL Injection using Heavy Queries (Parte II de II)
- Descargar Marathon Tool

Saludos Malignos!

jueves, agosto 14, 2008

Marathon Tool

La técnica de Time-Based Blind SQL Injection using Heavy Queries necesitaba una POC, así que había que realizar una herramienta que sacara partido de esto. Yo tenía más o menos escrito como quería hacerlo cuando Dani Kachakil me dijo algo como: "Ey, ¿puedo ir haciendo algo por ahí?".

De ahí surgió Marathon Tool, una herramienta que extrae datos de bases de datos a través de vulnerabilidades Blind SQL Injection explotadas en base a tiempos, es decir, si la respuesta es larga es Verdad y si la respuesta es corta es Falso, pero utilizando consultas pesadas. En las siguientes diapositivas, utilizadas en la Defcon, está un resumen de todo esto.

La herramienta está en versión alpha aun, pero es funcional en varios entornos y útil para explotar casi todas las situaciones.
Ahora mismo está preparada para MS SQL Server, Oracle, MySQL, MS Access 97/2000 y MS Access 2003/2007. Como se puede ver en la siguiente imagen, se configura de forma muy similar a Absinthe, es decir, primero la URL, luego los parámetros y se selecciona que parámetro es injectable. Las opciones permiten configurar cookies, sistemas de autenticación y un servidor proxy.

Configuración Injección

Como el método de extracción de datos basado en tiempos es bastante lento, la herramienta permite configurar varios parámetros para conseguir mejores rendimientos, así, primero se define que diferencia en tiemnpo debe haber entre una consulta Falsa y una consulta Verdadera. Esto le hará a la aplicación ir añadiendo copias de la tabla seleccionada a la consulta pesada para generar un mayor retardo en consultas TRUE. El resto de los parámetros son bastante sencillos de entender ya que son las pausas entre consultas, la pausa después de ejecutarse una consulta pesada (un valor TRUE), el número de tablas a probar, el time-out, etc...Como se puede ver, por defecto, cuando se selecciona un motor de bases de datos se utilizan unas tablas para intentar generar consultas pesadas, pero es editable y se puede utilizar cualquier tabla conocida o adivinada.

Parámetros de ajuste

Después de configurar el entorno, se inicializa la aplicación y se puede extraer el usuario o la extructura de la base de datos.

Extracción de datos

En caso de ser seleccionado un motor de base de datos MS Access, al no disponer en este de un diccionario de datos, se debe especificar la tabla y la columna de la que se quieren extraer valores.

La herramienta cuenta con un sistema de logs muy cómodo que permite distintos nivels de detalle. Si se selecciona el nivel 90 se verá, por ejemplo, un resumen de lo que la aplicación está realizando, y si se selecciona el nivel 100 se verán todas y cada una de las peticions al servidor web que se están realizando.

Sistema de Logs

La herramienta la está controlando ahora el bueno de Alekusu y está publicada, junto con el código fuente, en Codeplex. Está desarrollada en .NET utilizando Visual Basic.

A día de hoy está en versión alpha, funciona, pero no hemos podido realizar todas las pruebas necesarias, asi que, cualquier sugerencia, duda, o error que encontréis será bien recibido para poder mejorarla poco a poco. Descárgala de esta URL: Marathon Tool

Saludos Malignos!

miércoles, febrero 13, 2008

Arithmetic Blind SQL Injection

Hace tiempo, en un test de intrusión no autorizado, que eufemismo más chulo para decir que estaba haciendo…otra cosa, me encontré con un problema que me costó resolver. El entorno era el siguiente:

- Una base de datos Access en la que no podía poner comentarios.
- Una consulta en la que el parámetro inyectable estaba dentro de una función matemática que exigía como dato de entrada un número.
- Se usaba ese parámetro en dos condiciones where con distintos grados de anidación de paréntesis.

Vamos, era algo como:

Select campos
From tablas
Where (abs(parámetro_inyectable)=número) and abs(parámetro inyectable)=numero


Encontrar una cadena de inyección que construyera una consulta SQL correcta saliéndose de los paréntesis era completamente imposible pues si inyectabas algo del tipo: 1) and 1=1 and (abs(1 se obtenía algo como: Where (abs(1) and 1=1 and (abs(1 )=número) and abs(1) and 1=1 and (abs(1 )=numero

Quedaba perfectamente construida la query en la primera condición, pero no en la segunda. Y si inyectaba otra cadena que hiciera correcta la segunda query en paréntesis, la primera quedaba mal construida.

Bien, parecía imposible salir de la función matemática en la que estaba enjaulado. Así que había que hacer una lógica sin salirse de allí. Probé el método descrito por Litchfield en alguno de sus docs de hacer una lógica basándose en la división por 0, es decir, forzar que si algo era cierto diera división por cero. Es decir, el parámetro inyectable tenía que ser un número, entonces hagamos que la inyección siempre devuelva un número… o un error matemático.

parámetro_inyectable =if ((select user) = 'sa' OR (select user) = 'dbo') select 1 else select 1/0

Bueno, en Access no hay if, así que así no valía, había que realizar operaciones matemáticas a manopla para generar la división. Sin embargo, el método de la división por cero está bien cuando hay un if de por medio y cuando además el error es visible, pero… ¿por qué generar un error si podemos jugar con las mátematicas?

ensemos en el típico sistema de noticias dónde tenemos algo como noticias.php?id=14567 dónde queremos sacar, por ejemplo, la password del administrador de una supuesta tabla users.

- Si ponemos noticias.php?id=14567 obtenemos la Noticia 1
- Si ponemos noticias.php?id=abs(14567) y obtenemos la Noticia 1 entonces el sistema es inyectable.
- Si ponemos noticias.php?id=14566 obtenemos otra noticia.
- Si ponemos noticias.php?id?14567+60-(select ascii(mid(password,1,1)) from users) obtendremos la Noticia 1 si el primer caracter de la password es el que tiene ASCII 60 si no obtendremos otra noticia.

Esta forma de hacer un ataque por Blind SQL Injection permite no preocuparse por las anidaciones de paréntesis, no generar un error de división por cero (que puede tirar la sesión de la aplicación o generar una alerta XXL en la base de datos y además… funciona para todas las inyecciones con valores numéricos… Sencillo ¿no?

Saludos Malignos!

PD: ¿No quieres jugar con el Reto VI?

Entrada destacada

+300 referencias a papers, posts y talks de Hacking & Security con Inteligencia Artificial

Hace un mes comencé a recuperar en un post mi interés en los últimos años, donde he publicado muchos artículos en este blog , y he dejado mu...

Entradas populares