Por Willy Mejía [WillyXoft]
Este artículo muestra que és un ataque "SQL Injection"
y cómo evitar que
nuestras aplicaciones sean vulnerables al mismo.
Introducción.
"SQL Injection" es el nombre en inglés del
ataque contra un Gestor de Bases de Datos Relacional que aprovecha la
vulnerabilidad de una aplicación cliente del mismo. Dicha
vulnerabilidad consiste en permitir mandar instrucciones SQL
adicionales a partir de un campo o un
parámetro de entrada - por lo que se dice han sido "inyectadas".
La finalidad del ataque es realizar tareas sobre la
base de datos y de ser posible sobre host mismo, teniendo resultados
indeseables e inesperados que van desde la
alteración de un dato hasta apoderarse del servidor.
El ataque
"SQL Injection" es posible dadas ciertas características del
dialecto -o lenguaje- SQL que lo dotan de flexibilidad, tales como:
- Poder embeber comentarios en una sentencia SQL
- Poder escribir varias sentencias SQL juntas y ejecutarlas en
bloque.
- Poder realizar consultas de metadatos por medio de "tablas de
sistema".
Por este motivo cualquier RDBM que entiende SQL llámese
Oracle,
DB2,
SQL Server, MySQL, etc. es susceptible de recibir un ataque de este
tipo
a través de sus aplicaciones cliente - este ataque se produce a
nivel de aplicación- ya sean de tipo Consola, Windows o Web e
independiente de la plataforma de desarrollo (Java, Win32, .NET, etc.)
con la que fue elaborada.
Escenario vulnerable.
Veamos un caso hipotético de una aplicación de acceso
a datos que emplea entradas de usuario como parámetros de una
consulta SQL común. Es típico que este tipo de consultas
sean construidas
dinámicamente utilizando sentencias SQL con concatenación
de variables, al estilo:
"SELECT campo1, campo2,..., campoN FROM tablaX
WHERE campo1=" + mValor [+ ...]
Donde mValor esta dado por una entrada de usuario. Son
éstas entradas las puertas a un SQL Injection ya que,
dependiendo del tipo de dato de mValor, si en lugar de la entrada
esperada se coloca:
a) ' Or 1=1 --
b) 0 Or 1=1 --
c) #01/01/01# Or 1=1 --
Se puede realizar una consulta no deseada, ya que siguiendo el
primer caso, el resultado es la sentencia siguiente:
"SELECT campo1, campo2,..., campoN FROM tablaX
WHERE campo1='' Or 1=1 -- lo que siga no importa"
Lo que se consigue es de hacer válida la consulta al
añadir una cláusula OR que siempre será cierta
(1=1) así como de obligar al intérprete SQL a omitir el
resto de la sentencia SQL original al introducir el guión doble
(--) que le indica que lo subsiguiente es un comentario. Con ello el
atacante puede, por ejemplo, tener acceso a la aplicación sin
necesidad de contar con las credenciales adecuadas.
Por otro lado se podrían realizar consultas de
los metadatos, es decir de las tablas del sistema del gestor, para
conocer
los nombres de las tablas de usuario y sus respectivos campos. Para
ello podemos
utilizar la cláusula UNION y hacer coincidir el
número y los tipos de los campos de la consulta original de la
aplicación para
hacer algo como:
' UNION SELECT id, name,'', 0,'' FROM sysobjects
WHERE xtype='U' --
' UNION SELECT 0, name, '', 0,'' FROM syscolumns
WHERE id=[X] --
Con lo cual obtienen los nombres de las tablas de usuario y sus ID y
a través de éstos últimos también sus
columnas. Con dicha información es posible
realizar cualquier tipo de consulta sobre la base de datos, limitados
sólo por los privilegios de la cuenta con la que se realizan las
consultas SQL en la aplicación.
Y con privilegios de administrador se puede ir mas allá, ya
que algunos gestores como SQL Server tienen una serie de
procedimientos almacenados que
se pueden intentar ejecutar con esta técnica. Entre ellos
existen
algunos peligrosos como sp_OACreate y similares que permite la
creación y manipulación de
objetos ActiveX/COM y xp_cmdshell que permite ejecutar directamente
comandos
del sistema, por
ejemplo:
'; EXEC xp_cmdshell 'net stop sqlserver', no_output
Con lo visto hasta aquí es suficiente para ilustrar la manera
en que se realiza un ataque SQL
Injection, así como su alcance. Pasemos ahora a ver como
protegernos de dicho ataque.
Protegiéndonos.
Existen ciertos principios a considerar para proteger nuestras
aplicaciones de un SQL Injection:
- No confiar en la entrada del usuario.
- No utilizar sentencias SQL construidas dinámicamente.
- No utilizar cuentas con privilegios administrativos.
- No proporcionar mayor información de la necesaria.
A continuación veremos algunos ejemplos de como implementar
dichos
principios.
1. No confiar en la entrada del usuario significa:
- Filtrar la entrada del usuario de caracteres SQL para limitar los
caracteres involucrados en un SQL Injection.
Private Function SafeSqlLiteral( _
ByVal inputSQL As String) As String
Return inputSQL.Replace("'", "''")
End Function
'...
Dim safeSQL As String = SafeSqlLiteral(Login.Text)
Dim myCommand As SqlDataAdapter = _
New SqlDataAdapter("SELECT au_lname, au_fname " & _
"FROM authors WHERE au_id = '" & safeSQL & "'", _
myConnection)
- Proteger las instrucciones de búsqueda de
modelos
coincidentes (LIKE).
Private Function SafeSqlLikeClauseLiteral( _
ByVal inputSQL As String) As String
Dim s As String = inputSQL
s = inputSQL.Replace("'", "''")
s = s.Replace("[", "[[]")
s = s.Replace("%", "[%]")
s = s.Replace("_", "[_]")
Return s
End Function
- Verificar tanto el tamaño y como el tipo de los datos de
las entradas de usuario:
- Evitando los siguientes tipos caracteres de riesgo para el
gestor de datos:
- El delimitador de consultas: Punto y coma (;)
- Delimitador de datos de tipo cadena de caracteres:
Comilla sencilla (').
- Delimitadores de cometario: Guión doble (--) y
"/*..*/" en el caso de SQL Server.
Nota: En lugar de evitar los caracteres peligrosos,
otro modo de protegernos es aceptar sólo los caracteres
inofensivos.
- Evitando las cadenas con el inicio de nombres de las tablas y
los procedimientos del sistema: "sys" y "xp_" en el caso de SQL
Server. Así como las siguientes palabras: AUX, CLOCK$, COM1,
COM8, CON, CONFIG$, LPT1, LPT8, NUL y PRN
- Utilizar de preferencia controles con valores predefinidos o
discretos tales como cuadros de lista, cuadros
combinados, de verificación, etc. en lugar de cuadros de texto.
- Verificar cualquier tipo de entrada, no sólo lo
introducido en los controles IU sino también aquellas que no son
visibles, como parámetros de entrada y campos tipo hidden de las
páginas web.
- Realizar la verificación en todos los niveles y capas de
la aplicación, ya que si sólo protegemos la capa de
presentación somos vulnerables a que un atacante salte a la
siguiente capa y realizar su ataque.
2. No utilizar sentencias SQL construidas dinámicamente. En
lugar de ello:
- Utilizar instrucciones SQL con Parámetros.
Dim myCommand As SqlDataAdapter = New SqlDataAdapter( _
"SELECT au_lname, au_fname FROM Authors " & _
"WHERE au_id= @au_id", myConnection)
Dim parm As SqlParameter = _
myCommand.SelectCommand.Parameters.Add("@au_id", _
SqlDbType.VarChar, 11)
parm.Value = Login.Text
Aunque de hecho es mejor utilizar Procedimientos Almacenados
siempre que sea posible, así como también:
- Utilizar Parámetros al llamar Procedimientos Almacenados.
Dim myCommand As SqlDataAdapter = _
New SqlDataAdapter("AuthorLogin", myConnection)
myCommand.SelectCommand.CommandType = _
CommandType.StoredProcedure
Dim parm As SqlParameter = _
myCommand.SelectCommand.Parameters.Add("@LoginId", _
SqlDbType.VarChar,11)
parm.Value = Login.Text
3. No utilizar cuentas con privilegios administrativos.
- Ejecutar las sentencias SQL o invocar Procedimientos Almacenados
con una cuenta con privilegios
mínimos. Nunca emplear 'sa' en el caso de MS SQL Server. Pero
de preferencia además...
- Conceder permisos de ejecución únicamente a
Procedimientos Almacenados propios desde los cuales, a manera de
"wraper", se realicen las consultas a las Tablas de Usuario y llamados
a los Procedimientos Almacenados del Sistema que se requieran en las
aplicaciones, y negar el acceso directo a éstos últimos y
a las Tablas de Usuario.
4. No proporcionar mayor información de la necesaria.
- No exponer al usuario final los mensajes de error devueltos por
el
gestor de la base de datos, para no brindar mayor
información que sea útil al atacante.
- Implementar un sistema de gestión de errores que notifique
del mismo únicamente a los administradores de la
aplicación y el gestor de la base de datos. Por ejemplo, para el
caso de una aplicación web, establecer en el archivo de
configuración web (Web.Config) el valor del atributo debug del
elemento compilation en False y el atributo mode del elemento
customErrors en On, o en su defecto en RemoteOnly.
<compilation defaultLanguage="vb" debug="false" />
<customErrors mode="RemoteOnly"
defaultRedirect="GenericErrorPage.htm">
customErrors>
Conclusión.
Para prevenir un ataque SQL Injection, así como para
realizar pruebas (Testing) de vulnerabilidad contra el mismo en
nuestras
aplicaciones, no debemos olvidar que cualquier aplicación que
permita una "entrada" que sirva de parámetro para una consulta
SQL es vulnerable a éste ataque. Por ello en éste
artículo describimos brevemente de lo que se trata un SQL
Injection, así como de los principios básicos a seguir
para protegernos del mismo. Por último ejemplificamos algunas
implementaciones de dichos principios. Medidas a tomar para
protegerse de un ataque SQL Injection.
Última actualización: 28 de Agosto de
2005