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 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!