Parametrización de las consultas

Controles proactivos en el desarrollo seguro de software, Parte 2

Siguiendo con nuestra serie de posts relacionados con el desarrollo seguro de software integrando los controles proactivos de OWASP, vamos a hablar hoy del segundo punto de dichos controles proactivos: la parametrización de las consultas.

Indice

Nociones breves acerca de la necesidad de la parametrización de las queries:

Nociones breves acerca de SQL Injection:

¿Cómo funciona?

¿Qué problemas pueden causar este tipo de ataques?

Ejemplo de explotación:

¿Cómo se explota?

¿Cómo paliar o anular esta amenaza?

1. Escapar los caracteres especiales utilizados en las consultas SQL

2. Delimitar los valores de las consultas

3. Verificar siempre los datos que introduce el usuario

4. Asignar mínimos privilegios al usuario que conectará con la base de datos

5. Programar bien

6. Uso de sentencias preparadas parametrizadas.

Ejemplos en varios lenguajes

7. Uso de procedimientos almacenados.

Ejemplos en varios lenguajes

Nociones breves acerca de la necesidad de la parametrización de las queries:

La SQL Injection es una de las vulnerabilidades web más peligrosas, esto no es nuevo para ninguno de los que leemos estas páginas. Tanto es así que ha sido el elemento número 1 en el Top 10 de OWASP durante muchos años. Representa una amenaza seria ya que, entre otras acciones, la injección SQL permite la ejecución de código malicioso del atacante cambiando la estructura de la declaración SQL de una aplicación web de una manera tal, que puede robar datos, modificar datos, o potencialmente facilitar la inyección de comandos al sistema operativo subyacente. Asimismo, debemos tener en cuenta además que las sentencias SQL se ejecutan con rol de administrador con lo cual las consecuencias pueden llegar a ser desastrosas

Nociones breves acerca de SQL Injection:

¿Qué son los ataques SQL Injection? Según el proyecto OWASP: ”Un ataque por inyección SQL consiste en la inserción o “inyección” de una consulta SQL por medio de los datos de entrada desde el cliente hacia la aplicación. Un ataque por inyección SQL exitoso puede leer información sensible desde la base de datos, modificar la información (Insert/ Update/ Delete), ejecutar operaciones de administración sobre la base de datos (por ejemplo parar la base de datos), recuperar el contenido de un determinado archivo presente sobre el sistema de archivos del DBMS y en algunos casos emitir comandos al sistema operativo. Los ataques por inyección SQL son un tipo de ataque de inyección, en el cual los comandos SQL son insertados en la entrada de datos con la finalidad de efectuar la ejecución de comandos SQL predefinidos.

Es decir, permite insertar sentencias sql, con devastadoras consecuencias si se desea, allí donde en principio no se debiera. De hecho siempre está el en el Top Ten del proyecto desde hace muchos años, por algo será.

¿Pero esto por qué sucede? Porque la mayoría de las aplicaciones web desarrolladas hoy en día hacen uso de una base de datos para ofrecer páginas dinámicas y almacenar información, tanto de los usuarios como de la propia herramienta, datos a los que se accede por medio del lenguaje SQL, un lenguaje para interaccionar con bases de datos relacionales.

El uso de este tipo de lenguaje ha traído consigo la aparición de una de las vulnerabilidades más peligrosas a la que nos podemos enfrentar, una vulnerabilidad que no solamente pone en riesgo la integridad de la aplicación, sino de todos los datos almacenados de los usuarios que la utilicen, y que se produce cuando no se filtra de forma correcta la información enviada por los usuario.

¿Cómo funciona?

Mediante la inserción de código SQL por medio de los datos de entrada, desde la parte del cliente, hacia la aplicación. Es decir, por medio de la inserción de este código, el atacante puede modificar las consultas originales que debe realizar la aplicación y hacerla ejecutar otras totalmente distintas con la intención de acceder a las bases de datos, obtener información de alguna de las tablas o borrar los datos almacenados, entre otras muchas cosas. Como consecuencia directa de estos ataques y dependiendo de los privilegios que tenga el usuario de la base de datos bajo el que se ejecutan las consultas, se puede acceder no sólo a las tablas relacionadas con la aplicación, sino también a otras tablas, pertenecientes a otras bases de datos alojadas en ese mismo servidor, con lo que el posible impacto se puede magnificar.

Todo lo comentado en las líneas anteriores, es posible gracias al uso de ciertos caracteres en los campos de entrada de información por parte del usuario, ya sea mediante el uso de los campos de los formularios que son enviados al servidor mediante POST o bien por medio de los datos enviados mediante GET en las urls de las páginas web que posibilitan coordinar varias consultas SQL o ignorar el resto de la consulta, permitiendo al usuario malicioso ejecutar la consulta que elija, de ahí que sea de obligado cumplimiento, realizar un filtrado de esos datos enviados para evitar problemas en el futuro.

El atacante intentará provocar errores en la aplicación para, mediante la revisión de los logs de esos errores, extraer información que luego usará para insertar sus sentencias.

Cuando una aplicación controla los mensajes de error y no permite la impresión de ningún tipo de mensaje, se puede realizar otro tipo de ataque, el ataque “Blind SQL Injection”, traducido al español como “Ataque a ciegas por inyección de SQL”, que se da cuando en una página web no aparece ningún mensaje de error al ejecutar una sentencia SQL errónea, por lo que el atacante va realizando pruebas hasta dar con el nombre de los campos o tablas sobre los que poder actuar. Entre las bases de datos susceptibles a este tipo de ataques nos encontramos MySQL, Oracle, Postgres o MS SQL y MariaDB.

¿Qué problemas pueden causar este tipo de ataques?

Integridad: Al igual que un ataque por inyección SQL permite leer información relevante almacenada en la base de datos, también es posible realizar cambios o incluso borrar toda información mediante este tipo de vulnerabilidad. Se han dado casos de webs en las se han dumpeado las bases de datos y posteriormente, se han eliminado las que había, luego se han pedido rescates.

Confidencialidad: ¿Para qué se usan las BBDD? Las bases de datos almacenan información sensible, por lo que la pérdida de confiabilidad es un problema muy frecuente en aquellos sitios que son vulnerables a este tipo de ataques. Nadie vuelve a confiar en una plataforma a la que le han robado sus bases de datos.

Identificación: Si el sistema de logueo que se utiliza para acceder a una zona restringida de una web es débil, por medio de este tipo de ataques se podría acceder sin la necesidad de conocer ni el usuario ni la contraseña, en nuestro entorno de trabajo esto podría ser catastrófico en según qué circunstancias.

Identificación 2: Se pueden crear Usuarios que no existían anteriormente, con derechos de administrador y no conocidos. Podrían entrar quien quisiera, desde donde quisiera y hacer lo que desearan.

Ejemplo de explotación:

Este es un típico formulario donde se pide un nombre de usuario y una contraseña para acceder a una zona privada. El código del lado del servidor que forma la consulta sería algo como lo siguiente:

  1. $username = $_POST[‘username’];
  2. $password = $_POST[‘password’];
  3. $sql = ” SELECT * FROM usuarios WHERE username = ‘$username’ AND password = ‘$password’ “;

Como vemos, primero se almacenan el nombre de usuario y contraseña ingresadas, en las variables username y password respectivamente, seguidamente se incluyen estos valores en la consulta que será enviada a la base de datos para comprobar si existe dicho usuario. Supongamos en este caso que el usuario ingresa como nombre de usuario admin y contraseña pass123, al enviarse el formulario, la consulta formada sera la siguiente:

  1. SELECT * FROM usuarios WHERE username = ‘admin’ AND password = ‘pass123’

Como se ve, al colocarse los datos de entrada, estos quedan entre las comillas simples para representar que se trata de una cadena de texto. Esta consulta será enviada a la base de datos y nos devolverá un resultado con los datos de dicho usuario, si existe, y se permitirá el acceso a la zona privada, de lo contrario devolverá un resultado vacío y se impedirá negando el acceso.

Como se mencionó anteriormente, SQL injection consiste en anexar código SQL a una consulta, y este formulario lo permite mediante los campos de entrada, de forma que tenemos una aplicación vulnerable a SQL injection.

¿Cómo se explota?

El objeto de explotar esta vulnerabilidad es conseguir acceso a la zona privada sin conocer ni el usuario, ni contraseña correcta, y aprovechar la vulnerabilidad. De forma que lo que tenemos que lograr es inyectar código SQL para formar una consulta que retorne un resultado válido.

Veamos cómo queda formada la consulta si inyectamos el siguiente código SQL en el campo contraseña:

Al formarse la consulta, quedará de la siguiente manera:

  1. SELECT * FROM usuarios WHERE username = ‘hacker’ AND password = ” or 1=1#’

Hay que prestar atención en que el código insertado, se encuentra entre las comillas simples que encierran el password. La comilla simple al inicio del código insertado se encarga de completar la comilla abierta en la parte de la consulta password = ‘ , de esta forma obtenemos temporalmente la siguiente consulta:

  1. SELECT * FROM usuarios WHERE username = ‘hacker’ AND password = ”

Esta consulta de momento, no devolverá resultados pues no existe tal usuario con esas credenciales, sin embargo vamos a analizar el resto del código insertado:

  1. or 1=1#

La sentencia or 1=1 cambia radicalmente la lógica de la consulta, ya que como sabemos en una consulta formada por el condicional OR devolverá verdadero al cumplirse al menos una de las dos expresiones, en nuestro caso la primera expresión es username = ‘hacker’ AND password = ” , y la segunda or 1=1 , esta última se cumple siempre, es decir 1 es siempre igual a 1, por que la consulta nos devolverá un resultado válido.

Por ultimo tan solo queda deshacernos de la comilla que cierra la sentencia, para esto podemos usar los comentarios utilizados en SQL: # , — (doble guion), o bien /* */ . De esta forma la consulta completa es:

  1. SELECT * FROM usuarios WHERE username = ‘hacker’ AND password = ” or 1=1#’

Todo después del # será tomado en cuenta como comentario y no formará parte de la consulta.

Para conseguir obtener un resultado valido existen muchas otras variaciones en el código que podemos insertar, por ejemplo algunas de ellas…

¿Cómo podemos paliar o anular esta amenaza?

A la hora de desarrollar una aplicación, es muy complicado crear una herramienta totalmente segura a las primeras de cambio y si encima se “hereda” de otros desarrolladoresanteriores aún más, a esto puede añadirse versiones viejas de software, framework usados y ya sin soporte….etc. La falta de tiempo con las consiguientes presiones y la intervención de varios programadores para su desarrollo, son factores que también juegan en contra de la seguridad. A pesar de estos inconvenientes, siempre se pueden tomar medidas de seguridad que nos ayuden a desarrollar aplicaciones más robustas, ajenas en la medida de lo posible, a este tipo de problemas.

Vamos a ver a algunos consejos para evitar sufrir el ataque por inyección de código SQL en nuestros desarrollos o aplicaciones en producción:

Escapar los caracteres especiales utilizados en las consultas SQL

Al hablar de “escapar caracteres” estamos haciendo referencia a añadir la barra invertida “\” delante de las cadenas utilizadas en las consultas SQL para evitar que estas corrompan la consulta. Algunos de estos caracteres especiales que es aconsejable escapar son las comillas dobles (), las comillas simples () o los caracteres \x00 o \x1a ya que son considerados como peligrosos pues pueden ser utilizados durante los ataques.

Los distintos lenguajes de programación ofrecen mecanismos para lograr escapar estos caracteres. En el caso de PHP podemos optar por la función que toma como parámetro una cadena y la modifica evitando todos los caracteres especiales, devolviéndola totalmente segura para ser ejecutada dentro de la instrucción SQL. En Java la clase StringEscapeUtils, de la librería Lang del proyecto Apache Commons puede ayudar bastante, en según que circunstancias a escapar de según qué caracteres evitando con ello ataques de Inyección de SQL o XSS Cross-Site Scripting por poner varios ejemplos.

Delimitar los valores de las consultas

Aunque el valor de la consulta sea un entero, es aconsejable delimitarlo siempre entre comillas simples. Una instrucción SQL del tipo:

SELECT nombre FROM usuarios WHERE id_user = $id

Será mucho más fácilmente inyectable que:

SELECT nombre FROM usuarios WHERE id_user =$id

Donde $id es un número entero.

Verificar siempre los datos que introduce el usuario

Si en una consulta estamos a la espera de recibir un entero, no confiemos en que sea así, sino que es aconsejable tomar medidas de seguridad y realizar la comprobación de que realmente se trata del tipo de dato que estamos esperando. Para realizar esto, los lenguajes de programación ofrecen funciones que realizan esta acción, como pueden ser ctype_digit() para saber si es un número o ctype_alpha () para saber si se trata de una cadena de texto en el caso del lenguaje PHP.

También es aconsejable comprobar la longitud de los datos para descartar posibles técnicas de inyección SQL, ya que si por ejemplo estamos esperando un nombre, una cadena extremadamente larga puede suponer que estén intentando atacarnos por este método. En el caso de PHP, podemos utilizar la función strlen() para ver el tamaño de la cadena.

Asignar mínimos privilegios al usuario que conectará con la base de datos

El usuario que utilicemos para conectarnos a la base de datos desde nuestro código debe tener los privilegios justos para realizar las acciones que necesitemos. No utilizar nunca un usuario root (es tentador, lo se…) con acceso a todas las bases de datos ya que de esta forma estaremos dando facilidades a los hackers para que puedan acceder a toda la información.

Programar bien

Aunque pueda parecer una tontería, no hay mejor medida para evitar este tipo de ataques que realizar una buena programación, poniendo en práctica las necesidades básicas y el interés para desarrollar una aplicación totalmente segura. Obviamente esto debe ser tenido en cuenta desde el inicio del diseño de la aplicación, y si, llevará perder algo de tiempo, pero los resultados merecen la pena sobradamente y por otro lado implementar soluciones sobre la marcha o  (como suele ser lo más habitual desafortunadamente) después de entregar el producto y de haber tenido alguna  intrusión será mas costosa os lo puedo asegurar con total conocimiento.

Uso de sentencias preparadas parametrizadas.

Las consultas SQL parametrizadas se construyen utilizando secuencias de SQL regulares, pero en el momento de incluir datos suministrados por el usuario, incluyen parámetros de enlace, que son marcadores de posición para los datos, que se insertan posteriormente. En otras palabras, los parámetros bind permiten al programador especificar explícitamente a la base de datos lo que debe ser tratado como un comando y lo que debe ser tratado como datos. Cuando el programa está listo para ejecutar una sentencia, especifica a la base de datos, los valores de tiempo de ejecución a utilizar para cada uno de los parámetros de enlace, sin el riesgo de que los datos se interpreten como una modificación del comando.

Bastantes frameworks de desarrollo (Rails, Django, Node.js, etc.) emplean un modelo objeto-relacional (ORM) para la comunicación abstracta con una base de datos. Muchos ORMs proporcionan parametrización automática de consultas cuando se utilizan métodos programáticos para recuperar y modificar datos, pero esto también implica que los desarrolladores deben tener cuidado al permitir la entrada de usuarios en consultas de objetos (OQL/HQL) u otras consultas avanzadas soportadas por el framework en cuestión.

Con  esta técnica, la consulta SQL se envía por separado de cualquier parámetro malicioso que pudiera suponer un peligro para el sistema. Por último, si es posible, los motores de base de datos deben configurarse para que solamente soporten consultas parametrizadas, es por eso que es tan importante el diseño y planteamiento inicial de las aplicaciones teniendo en cuenta la seguridad, nunca se debe perder eso de vista en los desarrollos.

Otra alternativa, también parametrizando las consultas podría realizarse por ejemplo usando la librería OWASP ESAPI, (que sin ser la panacea) puede llegar a ser muy útil.

SQL VULNERABLE

// Validación de datos de entrada con ESAPI.

userId= ESAPI.encoder().encodeForSQL(new MySQLCodec(MySQLCodec.Mode.STANDARD),

request.getParameter(“userId “));

String sqlString=”SELECT * FROM users WHERE userId = ’”+ form.getUserId()+”’”;

SQL SEGURA.

// Validación de datos de entrada con ESAPI.

userId= ESAPI.encoder().encodeForSQL(new MySQLCodec(MySQLCodec.Mode.STANDARD),

request.getParameter(“userId “));

// Consulta Preparada.

String consulta = “SELECT * FROM users WHERE userId = ?;”;

PreparedStatement pstmt = connection.prepareStatement (consulta);

pstmt.setString( 1, custname);pstmt.setString (1, userId);

ResultSet results = pstmt.executeQuery( );

Ejemplos en varios lenguajes

Language – Library Parameterized Query
Java – Standard String custname = request.getParameter(“customerName”);

String query = “SELECT account_balance FROM user_data WHERE user_name = ? “;

PreparedStatement pstmt = connection.prepareStatement( query );

pstmt.setString( 1, custname);

ResultSet results = pstmt.executeQuery( );

Java – Hibernate //HQL

@Entity // declare as entity;

@NamedQuery(

name=”findByDescription”,

query=”FROM Inventory i WHERE i.productDescription = :productDescription”

)

public class Inventory implements Serializable {

@Id

private long id;

private String productDescription;

}

// use case

String userSuppliedParameter = request.getParameter(“Product-Description”); // This should REALLY be validated too

// perform input validation to detect attacks

List list =

session.getNamedQuery(“findByDescription”)

.setParameter(“productDescription”, userSuppliedParameter).list();

//Criteria API

String userSuppliedParameter = request.getParameter(“Product-Description”); // This should REALLY be validated too

// perform input validation to detect attacks

Inventory inv = (Inventory) session.createCriteria(Inventory.class).add

(Restrictions.eq(“productDescription”, userSuppliedParameter)).uniqueResult();

.NET/C# String query = “SELECT account_balance FROM user_data WHERE user_name = ?”;

try {

OleDbCommand command = new OleDbCommand(query, connection);

command.Parameters.Add(new OleDbParameter(“customerName”, CustomerName Name.Text));

OleDbDataReader reader = command.ExecuteReader();

// …

} catch (OleDbException se) {

// error handling

}

ASP.NET string sql = “SELECT * FROM Customers WHERE CustomerId = @CustomerId”;

SqlCommand command = new SqlCommand(sql);

command.Parameters.Add(new SqlParameter(“@CustomerId”, System.Data.SqlDbType.Int));

command.Parameters[“@CustomerId”].Value = 1;

Ruby – ActiveRecord # Create

Project.create!(:name => ‘owasp’)

# Read

Project.all(:conditions => “name = ?”, name)

Project.all(:conditions => { :name => name })

Project.where(“name = :name”, :name => name)

# Update

project.update_attributes(:name => ‘owasp’)

# Delete

Project.delete(:name => ‘name’)

Ruby insert_new_user = db.prepare “INSERT INTO users (name, age, gender) VALUES (?, ? ,?)”

insert_new_user.execute ‘aizatto’, ’20’, ‘male’

PHP – PDO $stmt = $dbh->prepare(“INSERT INTO REGISTRY (name, value) VALUES (:name, :value)”);

$stmt->bindParam(‘:name’, $name);

$stmt->bindParam(‘:value’, $value);

Cold Fusion  

SELECT * FROM #strDatabasePrefix#_courses WHERE intCourseID =

 

 

Perl – DBI my $sql = “INSERT INTO foo (bar, baz) VALUES ( ?, ? )”;

my $sth = $dbh->prepare( $sql );

$sth->execute( $bar, $baz );

Uso de procedimientos almacenados.

Los procedimientos almacenados no incluyen ninguna generación dinámica de SQL ya que se encuentran almacenados en Base de Datos. Usemos en este ejemplo, la anteriormente mencionada ESAPI

// Validación de datos de entrada con ESAPI

String idCliente = ESAPI.encoder().encodeForSQL(newMySQLCodec(MySQLCodec.Mode.STANDARD),

request.getParameter(“idCliente”));

try {

CallableStatement cs = connection.prepareCall(“{call sp_getUsuario(?)}”);

cs.setString(1, idCliente);

ResultSet results = cs.executeQuery();

// … resultado

} catch (SQLException se) {

// …tratar error.

}

Ejemplos en varios lenguajes

Language – Library Parameterized Query
Oracle – PL/SQL Normal Stored Procedure – no dynamic SQL being created. Parameters passed in to stored procedures are naturally bound to their location within the query without anything special being required.

PROCEDURE SafeGetBalanceQuery(

UserID varchar, Dept varchar) AS BEGIN

SELECT balance FROM accounts_table WHERE user_ID = UserID AND department = Dept;

END;

Oracle – PL/SQL Stored Procedure Using Bind Variables in SQL Run with EXECUTE. Bind variables are used to tell the database that the inputs to this dynamic SQL are ‘data’ and not possibly code.

PROCEDURE AnotherSafeGetBalanceQuery(

UserID varchar, Dept varchar) AS

stmt VARCHAR(400); result NUMBER;

BEGIN

stmt := ‘SELECT balance FROM accounts_table WHERE user_ID = :1

AND department = :2’;

EXECUTE IMMEDIATE stmt INTO result USING UserID, Dept;

RETURN result;

END;

SQL Server- Transact-SQL Normal Stored Procedure – no dynamic SQL being created. Parameters passed in to stored procedures are naturally bound to their location within the query without anything special being required.

PROCEDURE SafeGetBalanceQuery(

@UserID varchar(20),

@Dept varchar(10)) AS BEGIN

SELECT balance FROM accounts_table WHERE user_ID = @UserID AND department = @Dept

END

SQL Server- Transact-SQL Stored Procedure Using Bind Variables in SQL Run with EXEC. Bind variables are used to tell the database that the inputs to this dynamic SQL are ‘data’ and not possibly code.

PROCEDURE SafeGetBalanceQuery(@UserID varchar(20),

@Dept varchar(10)) AS BEGIN

DECLARE @sql VARCHAR(200)

SELECT @sql = ‘SELECT balance FROM accounts_table WHERE ‘

+ ‘user_ID = @UID AND department = @DPT’

EXEC sp_executesql @sql,

‘@UID VARCHAR(20), @DPT VARCHAR(10)’,

@UID=@UserID, @DPT=@Dept

END

 

2 respuesta a “Parametrización de las consultas”

  1. Madre mía, otro igual, qué barbaridad de entradas.

    A todo esto no sé si os lo han dicho pero el “índice” no funciona, entiendo que al pinchar en algo debería llevarte a esa parte en concreto y al menos a mi no me pasa, ni en Firefox ni en Chrome, ambos en Linux Mint.

    1. Mestre Liamngls como siempre es un placer saber de usted. No se que ha pasado, pero en cuanto tenga un rato libre lo miro y lo arreglo inmediatamente. Muchas gracias por el reporte y por pasarte por esta, tu casa. Un saludo

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.