GuilleSQL :: Microsoft SQL Server, SSIS, y más !!

SQL Injection, Procedimientos Almacenados, ASP, ADO y SQL Server

Volver a: [SQL Injection]


Este capítulo explica cómo utilizar los Procedimientos Almacenados en SQL Server para evitar ataques SQL Injection, comparando las dos alternativas posibles de uso: utilizar procedimientos almacenados sin consultas parametrizadas, y utilizar procedimientos almacenados con consultas parametrizadas (opción recomendada). Para ello se incluye código ASP de ejemplo (llamadas ADODB.Connection, ADODB.Recordset, ADODB.Command) sobre el que se explican los ejemplos de ataques SQL Injection, razonando su funcionamiento y consecuencias.

Quizás el principal método de protección contra SQL Injection sea la utilización de Procedimientos Almacenados para realizar todos los accesos a base de datos (al menos en el caso de SQL Server y SQL Injection). Sin embargo, no es suficiente con utilizar Procedimientos Almacenados, ya que en función de cómo utilicemos los Procedimientos Almacenados en nuestra aplicación conseguiremos protegernos de SQL Injection o seguiremos al descubierto ante ataques de SQL Injection.

A continuación se ilustran dos ejemplos de utilización de Procedimientos Almacenados para acceder a SQL Server desde una Aplicación ASP, teniéndose en cuenta que estos ejemplos son aplicables tanto a Aplicaciones Windows (ej: aplicaciones Visual Basic 6) como a Aplicaciones .Net (tanto WebForms como WinForms), y cualquier otro tipo de aplicaciones (aplicaciones Java, componentes COM+, etc.).

Para la realización de estos ejercicios, hemos creado en el Laboratorio de GuilleSQL un Procedimiento Almacenado en SQL Server, y dos páginas ASP: una página ASP llamará al Procedimiento Almacenado sin utilizar Consultas Parametrizadas, y las otra página ASP llamará al Procedimiento Almacenado utilizando Consultas Parametrizadas. ¿Qué diferencias encontraremos? Sigue leyendo...

Ejemplo 1. Utilizar Procedimientos Almacenados sin Consultas Parametrizadas

En este primer ejemplo, y continuando con el escenario utilizado anteriormente en este artículo (un formulario Web de inicio de sesión en una Aplicación ASP), vamos a utilizar el mismo código empleado en los ejemplos anteriores del Laboratorio GuilleSQL, con la única diferencia de que el acceso a la base de datos lo realizaremos a través de una llamada a un Procedimiento Almacenado (en el ejemplo, el procedimiento spGetUsuario). Eso sí, en este primer ejemplo no vamos a utilizar Consultas Parametrizadas, por lo tanto, nos vamos a limitar a sustituir el texto de la sentencia SELECT utilizada anteriormente por el texto de la llamada al Procedimiento Almacenado. Esto es lo equivalente a utilizar en java el interfaz java.sql.Statement. El código resultante es el siguiente:

<%
strUsuario = Request.Form("txtUsuario")
strPassword = Request.Form("txtPassword")

strSQL = "GuilleSQL.spGetUsuario '" & strUsuario & "', '" & strPassword & "'"

Set miCon = Server.CreateObject("ADODB.Connection")
miCon.ConnectionString = "Provider=SQLOLEDB;Data Source=GuilleXP;trusted_connection=yes;Initial Catalog=GuilleSQL"
miCon.Open

Set miRS = Server.CreateObject("ADODB.Recordset")
miRS.Open strSQL, miCon

'****
'**** (c) www.guillesql.es
'**** Controlar aquí si existe el Usuario con miRS.EOF en un IF
'**** y tomar los datos del Usuario (desde miRS), si miRS.EOF es falso
'****

miRS.Close
miCon.Close

Set miRS = Nothing
Set miCon = Nothing
%>

Como se puede observar, seguimos ante el mismo problema, ya que al fin y al cabo seguimos construyendo una cadena de texto dinámicamente que seguidamente será ejecutada por SQL Server (o por el motor de base de datos que estemos utilizando), y en consecuencia, seguimos siendo vulnerables a la introducción de cadenas peligrosas por parte del atacante (Hacker).

También es cierto, que el simple hecho de utilizar Procedimientos Almacenados, tiene ciertas diferencias de comportamiento, que nos protege de algunos de los casos que vimos anteriormente (más o menos). Así, si volvemos a unos de los ejemplos anteriores, en que un usuario introduce los siguientes valores de entrada:

  • txtUsuario: ' OR 'A'='A
  • txtPassword: ' OR 'A'='A

Si sustituimos estos valores en la expresión que se utiliza para generar la consulta SQL que se ejecutará contra la base de datos, obtendremos la siguiente cadena:

GuilleSQL.spGetUsuario '' OR 'A'='A', '' OR 'A'='A'

En este caso, el atacante ha fracasado, pues realmente se está intentando ejecutar una cadena sintácticamente incorrecta, lo cual producirá un error en el acceso a SQL Server (y así lo hemos comprobado en el Laboratorio de GuilleSQL). En consecuencia, nos hemos protegido ante el ataque (malamente, pues ha sido pagando el precio de conseguir un error de sintaxis de SQL Server). Evidentemente, resulta vital una correcta gestión de errores, y sobre todo, evitar mostrar los errores de acceso a base de datos al Usuario, ya que esto podría ser utilizado por un Usuario atacante (Hacker) como método para revelar parcial o totalmente el esquema y configuración de la base de datos que está atacando, y así poder afinar sus siguientes intentos de ataque.

Desde este punto de vista, parece que hemos mejorado (muy poco, por cierto), pero esto no es del todo cierto, ya que el atacante (Hacker) simplemente deberá de utilizar otro tipo de expresiones para aprovechar nuestra vulnerabilidad y progresar en su ataque. Supongamos que un usuario introduce los siguientes valores de entrada:

  • txtUsuario: loquesea
  • txtPassword: '; DROP TABLE GuilleSQL.USUARIOS; --

Entonces, si sustituimos estos valores en la expresión que se utiliza para generar la consulta SQL que se ejecutará contra la base de datos, obtendremos la siguiente consulta:

GuilleSQL.spGetUsuario 'loquesea', ''; DROP TABLE GuilleSQL.USUARIOS; --'

En este nuevo ejemplo, el ataque del usuario (Hacker) progresará y tendrá éxito (si el usuario o login utilizado para acceder a SQL Server tiene permisos suficientes, claro ;-). Si examinamos la consulta SQL que será ejecutada por SQL Server, podemos identificar claramente tres sentencias SQL:

  • Llamada al procedimiento GuilleSQL.spGetUsuario que no devolverá filas: GuilleSQL.spGetUsuario 'loquesea','';
  • Consulta SQL maliciosa e invasora: DROP TABLE GuilleSQL.USUARIOS;
  • Un simple comentario, para evitar errores de sintaxis en el ataque: --'

Con este ejemplo, conseguimos demostrar de forma empírica que aún utilizando Procedimientos Almacenados, seguimos siendo vulnerables a ataques de SQL Injection, al menos en el caso de no utilizar Consultas Parametrizadas.

Ejemplo 2. Utilizar Procedimientos Almacenados con Consultas Parametrizadas

En este segundo ejemplo, vamos a realizar el acceso a la base de datos a través de una llamada a un Procedimiento Almacenado, pero a diferencia del caso anterior, en este caso vamos a utilizar Consultas Parametrizadas. Con este método, evitamos tener que construir la consulta SQL como una concatenación de varias cadenas (es aquí dónde está el peligro), de tal modo, que al utilizar Consultas Parametrizadas y Procedimientos Almacenados se realizará una simple sustitución en los valores de los parámetros al realizar la llamada al Procedimiento Almacenado (sin concatenaciones, es decir, sin peligro). Además, como veremos en las pruebas realizadas en el Laboratorio de GuilleSQL, la utilización de parámetros nos permite introducir implícitamente algunas validaciones de tipo y rango. Esto es lo equivalente a utilizar en java el interfaz java.sql.PreparedStatement. El código resultante es el siguiente:

<%
strUsuario = Request.Form("txtUsuario")
strPassword = Request.Form("txtPassword")

Set miCon = Server.CreateObject("ADODB.Connection")
miCon.ConnectionString = "Provider=SQLOLEDB;Data Source=GuilleXP;trusted_connection=yes;Initial Catalog=GuilleSQL"
miCon.Open

Set miCom = Server.CreateObject("ADODB.Command")
Set miCom.Activeconnection = miCon

miCom.commandText="GuilleSQL.spGetUsuario"
miCom.commandtype = 4
miCom.Parameters.Append miCom.CreateParameter("@USU_ID",3,1,,1)
miCom.Parameters.Append miCom.CreateParameter("@USU_PWD",3,1,,12)

Set miRS = miCom.Execute

'****
'**** (c) www.guillesql.es
'**** Controlar aquí si existe el Usuario con miRS.EOF en un IF
'**** y tomar los datos del Usuario (desde miRS), si miRS.EOF es falso
'****

miRS.Close
miCon.Close

Set miRS = Nothing
Set miCon = Nothing
%>

En esta ocasión sí que estamos protegidos ante ataques SQL Injection, ya que no construimos dinámicamente la cadena de texto SQL a ejecutar (eso sí, estamos protegidos siempre y cuando el procedimiento almacenado no utilice SQL Dinámico - requisito indispensable - , pues en dicho caso seguiremos bajo riesgo). Así, si volvemos a uno de los ejemplos anteriores, en que un usuario introduce los siguientes valores de entrada:

  • txtUsuario: ' OR 'A'='A
  • txtPassword: ' OR 'A'='A

La llamada al procedimiento almacenado no devolverá filas (comprobado en el Laboratorio de GuilleSQL), excepto que realmente existe un usuario con nombre ' OR 'A'='A y contraseña ' OR 'A'='A, con lo cual conseguimos demostrar que realmente ahora sí que estamos protegidos frente a ataques de SQL Injection. El atacante (Hacker) no ha conseguido su cometido, pues estaba intentando debilitar una consulta SQL con la introducción maliciosa de disyunciones lógicas (cláusulas OR) en la cláusula WHERE, y no ha conseguido el efecto que esperaba.

Es importante recordar que la utilización de parámetros implica la realización implícita de comprobaciones de tipo y rango. Por ejemplo, si especificamos una cadena demasiado larga (es decir, el texto introducido es más largo que la longitud máximo del parámetro - ej: VARCHAR de 20) se obtendrá un error como el siguiente:

ADODB.Command (0x800A0D5D)
La aplicación utiliza un valor de tipo no válido para la operación actual.

En inglés:

ADODB.Command (0x800A0D5D)
Application uses a value of the wrong type for the current operation.

Evidentemente, vuelve a resultar vital una correcta gestión de errores, y sigue siendo muy recomendable evitar mostrar los errores de acceso a base de datos al Usuario.

Conclusiones

Con todo esto, queda claro que no es suficiente con utilizar Procedimientos Almacenados para acceder a nuestra base de datos SQL Server, sino que además debemos acceder a dichos Procedimientos Almacenados a través de Consultas Parametrizadas, pues en caso contrario seguiremos al descubierto frente a ataques SQL Injection.

Pero la historia no acaba aquí, ya que el código existente dentro del Procedimiento Almacenado también podría ser nuestro talón de Aquiles, como es el caso de la utilización de código SQL Dinámico dentro del Procedimiento Almacenado. En este caso, si utilizamos SQL Dinámico dentro del Procedimiento Almacenado, y para construir la sentencia SQL que deseamos ejecutar recurrimos a la concatenación de variables, volveremos a estar en peligro, debiendo utilizar mecanismos adicionales para protegernos de ataques SQL Injection, como se explica más adelante en este artículo (ej: validar los datos de entrada, sustituir cadenas peligrosas, etc.).

Conclusión: para protegernos de ataques SQL Injection es recomendable utilizar Procedimientos Almacenados para todos los accesos a base de datos, accediendo a dichos Procedimientos Almacenados a través de Consultas Parametrizadas (desde ADO, ADO.Net, etc.), y evitando la utilización de SQL Dinámico en el interior de dichos Procedimientos Almacenados. En caso de utilizar código SQL Dinámico en el interior de dichos Procedimientos Almacenados, será necesario utilizar mecanismos adicionales para protegernos de ataques SQL Injection artículo (ej: validar los datos de entrada, sustituir cadenas peligrosas, etc.).

Volver a: [SQL Injection]




Miembros de
Miembros de GITCA (Global IT Community Association)

Menu de Usuario
  Iniciar Sesión
  Registrarse
  Restablecer Contraseña
  Ventajas de Registrarse

Acerca de
  Contigo desde Oct 2007
  771 usuarios registrados
  86146 pageloads/mes
  Ranking Alexa 498160

Social Networks
Sigue a Portal GuilleSQL en Linkedin !!
Sigue a Portal GuilleSQL en Twitter !!



Archivo

Marzo de 2019 (1)
Octubre de 2018 (1)
Julio de 2018 (1)
Junio de 2018 (4)
Mayo de 2018 (5)
Abril de 2018 (3)
Marzo de 2018 (2)
Febrero de 2018 (7)
Enero de 2018 (1)
Diciembre de 2017 (15)
Noviembre de 2017 (7)
Junio de 2017 (3)
Mayo de 2017 (1)
Marzo de 2017 (3)
Enero de 2017 (4)
Junio de 2016 (1)
Mayo de 2016 (2)
Abril de 2016 (2)
Septiembre de 2015 (2)
Agosto de 2015 (2)
Junio de 2015 (10)
Mayo de 2015 (4)
Abril de 2015 (8)
Marzo de 2015 (11)
Octubre de 2014 (3)
Septiembre de 2014 (7)
Agosto de 2014 (5)
Julio de 2014 (2)
Mayo de 2014 (4)
Abril de 2014 (4)
Marzo de 2014 (4)
Febrero de 2014 (1)
Enero de 2014 (5)
Diciembre de 2013 (8)
Noviembre de 2013 (2)
Octubre de 2013 (7)
Septiembre de 2013 (6)
Agosto de 2013 (1)
Julio de 2013 (6)
Junio de 2013 (11)
Mayo de 2013 (7)
Abril de 2013 (6)
Febrero de 2013 (5)
Enero de 2013 (7)
Diciembre de 2012 (12)
Noviembre de 2012 (13)
Octubre de 2012 (5)
Septiembre de 2012 (3)
Agosto de 2012 (6)
Julio de 2012 (4)
Junio de 2012 (1)
Mayo de 2012 (2)
Abril de 2012 (7)
Marzo de 2012 (16)
Febrero de 2012 (9)
Enero de 2012 (5)
Diciembre de 2011 (10)
Noviembre de 2011 (10)
Octubre de 2011 (4)
Septiembre de 2011 (5)
Agosto de 2011 (2)
Julio de 2011 (2)
Junio de 2011 (4)
Mayo de 2011 (2)
Abril de 2011 (6)
Marzo de 2011 (4)
Febrero de 2011 (10)
Enero de 2011 (5)
Diciembre de 2010 (6)
Noviembre de 2010 (4)
Octubre de 2010 (8)
Septiembre de 2010 (4)
Agosto de 2010 (1)
Julio de 2010 (3)
Mayo de 2010 (5)
Abril de 2010 (6)
Marzo de 2010 (8)
Febrero de 2010 (3)
Enero de 2010 (1)
Diciembre de 2009 (9)
Noviembre de 2009 (14)
Octubre de 2009 (2)
Septiembre de 2009 (8)
Agosto de 2009 (2)
Julio de 2009 (10)
Junio de 2009 (9)
Mayo de 2009 (10)
Abril de 2009 (9)
Marzo de 2009 (3)
Febrero de 2009 (2)
Enero de 2009 (3)
Noviembre de 2008 (2)
Octubre de 2008 (2)
Septiembre de 2008 (2)
Agosto de 2008 (5)
Julio de 2008 (5)
Junio de 2008 (1)
Mayo de 2008 (3)
Abril de 2008 (2)
Marzo de 2008 (2)
Febrero de 2008 (2)
Enero de 2008 (5)
Noviembre de 2007 (2)
Octubre de 2007 (2)






Copyright © 2007 GuilleSQL, todos los derechos reservados.