Prevención de inyecciones SQL en Java con JPA e Hibernate

publicado original en inglés en DZone 09.2022

Cuando echamos un vistazo al top 10 de vulnerabilidades de OWASP [1], las Inyecciones SQL siguen en una posición popular. En este breve artículo, discutimos varias opciones sobre cómo las Inyecciones SQL pueden ser evitadas.

Cuando las aplicaciones tienen que lidiar con bases de datos existentes siempre preocupaciones de alta seguridad, si un invasor tiene la posibilidad de secuestrar la capa de base de datos de su aplicación, puede elegir entre varias opciones. Robar los datos de los usuarios almacenados para inundarlos de spam no es el peor escenario que podría darse. Aún más problemático sería que se abusara de la información de pago almacenada. Otra posibilidad de un ciberataque SQL Injection es conseguir acceso ilegal a contenidos y/o servicios de pago restringidos. Como podemos ver, hay muchas razones por las que preocuparse por la seguridad de las Aplicaciones (Web).

Para encontrar medidas preventivas eficaces contra las inyecciones SQL, primero debemos comprender cómo funciona un ataque de inyección SQL y a qué puntos debemos prestar atención. En resumen: cada interacción de usuario que procesa la entrada sin filtrar en una consulta SQL es un posible objetivo para un ataque. La entrada de datos puede ser manipulada de manera que la consulta SQL enviada contenga una lógica diferente a la original. El listado 1 le dará una buena idea de lo que podría ser posible.

SELECT Username, Password, Role FROM User
   WHERE Username = 'John Doe' AND Password = 'S3cr3t';
SELECT Username, Password, Role FROM Users
   WHERE Username = 'John Doe'; --' AND Password='S3cr3t';
SELECT Username, Password, Role FROM User
   WHERE Username = 'John Doe' AND Password = 'S3cr3t';
SELECT Username, Password, Role FROM Users
   WHERE Username = 'John Doe'; --' AND Password='S3cr3t';
SQL
SQL

Listing 1: Simple SQL Injection

La primera sentencia del Listado 1 muestra la consulta original. Si no se filtra la entrada para las variables Username y Password, tenemos una falta de seguridad. La segunda consulta inyecta para la variable Username un String con el nombre de usuario John Doe y se extiende con los caracteres ‘; -. Esta sentencia se salta la rama AND y da, en este caso, acceso al login. La secuencia ‘; cierra la sentencia WHERE y con – todos los caracteres siguientes quedan sin comentar. Teóricamente, es posible ejecutar entre ambas secuencias de caracteres cualquier código SQL válido.

Por supuesto, mi plan no es difundir las ideas de que los comandos SQL podrían suscitar las peores consecuencias para la víctima. Con este simple ejemplo, asumo que el mensaje es claro. Necesitamos proteger cada variable de entrada UI en nuestra aplicación contra la manipulación del usuario. Incluso si no se utilizan directamente para consultas a la base de datos. Para detectar esas variables, siempre es una buena idea validar todos los formularios de entrada existentes. Pero las aplicaciones modernas suelen tener más que unos pocos formularios de entrada. Por esta razón, también menciono mantener un ojo en sus puntos finales REST. A menudo sus parámetros también están conectados con consultas SQL.

Por esta razón, la validación de entradas, en general, debería formar parte del concepto de seguridad. Las anotaciones de la especificación Bean Validation [2] son, para este propósito, muy potentes. Por ejemplo, @NotNull, como Anotación para el campo de datos en el objeto de dominio, asegura que el objeto sólo es capaz de persistir si la variable no está vacía. Para utilizar las Anotaciones de Validación de Bean en tu proyecto Java, sólo necesitas incluir una pequeña librería.

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>${version}</version>
</dependency>
XML

Listing 2: Maven Dependency para Bean Validation

Quizá sea necesario validar estructuras de datos más complejas. Con las expresiones regulares, tiene otra poderosa herramienta en sus manos. Pero tenga cuidado. No es tan fácil escribir una RegEx que funcione correctamente. Veamos un pequeño ejemplo.

public static final String RGB_COLOR = "#[0-9a-fA-F]{3,3}([0-9a-fA-F]{3,3})?";

public boolean validate(String content, String regEx) {
    boolean test;
    if (content.matches(regEx)) {
        test = true;
    } else {
        test = false;
    }
    return test;
}

validate('#000', RGB_COLOR);
Java

Listing 3: Validación mediante expresiones regulares en Java

El RegEx para detectar el esquema de color RGB correcto es bastante simple. Las entradas válidas son #ffF o #000000. El rango para los caracteres es 0-9, y las letras A a F. Insensible a mayúsculas y minúsculas. Cuando desarrollas tu propio RegEx, siempre necesitas comprobar muy bien los límites existentes. Un buen ejemplo es también el formato de 24 horas. Errores típicos son entradas inválidas como 23:60 o 24:00. El método validate compara la cadena de entrada con el RegEx. Si el patrón coincide con la entrada, el método devolverá true. Si quieres obtener más ideas sobre validadores en Java, también puedes consultar mi repositorio de GitHub [3].

En resumen, nuestra primera idea para asegurar la entrada del usuario contra el abuso es filtrar todas las secuencias de caracteres problemáticas, como – y así sucesivamente. Bueno, esta intención de crear una lista de bloqueo no es tan mala. Pero sigue teniendo algunas limitaciones. Al principio, la complejidad de la aplicación aumentó porque bloquear caracteres individuales como -; y ‘ podría causar a veces efectos secundarios no deseados. Además, una limitación de caracteres por defecto en toda la aplicación puede causar problemas. Imagina que hay un área de texto para un sistema de Blog o algo similar.

Esto significa que necesitamos otro concepto potente para filtrar la entrada de forma que nuestra consulta SQL no pueda manipularla. Para alcanzar este objetivo, el estándar SQL tiene una gran solución que podemos utilizar. Los Parámetros SQL son variables dentro de una consulta SQL que serán interpretadas como contenido y no como una sentencia. Esto permite que los textos grandes bloqueen algunos caracteres peligrosos. Echemos un vistazo a cómo funcionará esto en una base de datos PostgreSQL [4].

DECLARE user String;
SELECT * FROM login WHERE name = user;
SQL

Listing 4: Definición de parámetros en PostgreSQL

En el caso de que esté utilizando el mapeador OR Hibernate, existe una forma más elegante con la Java Persistence API (JPA).

String myUserInput;

@PersistenceContext
public EntityManager mainEntityManagerFactory;

CriteriaBuilder builder =
    mainEntityManagerFactory.getCriteriaBuilder();

CriteriaQuery<DomainObject> query =
    builder.createQuery(DomainObject.class);

// create Criteria
Root<ConfigurationDO> root =
    query.from(DomainObject.class);

//Criteria SQL Parameters
ParameterExpression<String> paramKey =
    builder.parameter(String.class);

query.where(builder.equal(root.get("name"), paramKey);

// wire queries together with parameters
TypedQuery<ConfigurationDO> result =
    mainEntityManagerFactory.createQuery(query);

result.setParameter(paramKey, myUserInput);
DomainObject entry = result.getSingleResult();
Java

Listing 5: Uso de parámetros SQL de Hibernate JPA

El listado 5 se muestra como un ejemplo completo de Hibernate utilizando JPA con la API de criterios. La variable para la entrada del usuario se declara en la primera línea. Los comentarios en el listado explican cómo funciona. Como puedes ver, no es ninguna ciencia espacial. La solución tiene otras ventajas además de mejorar la seguridad de la aplicación web. Al principio, no se utiliza SQL plano. Esto asegura que cada sistema de gestión de bases de datos soportado por Hibernate puede ser asegurado por este código.

Puede que el uso parezca un poco más complejo que una simple consulta, pero el beneficio para tu aplicación es enorme. Por otro lado, por supuesto, hay algunas líneas extra de código. Pero no son tan difíciles de entender.

Recursos

Links are only visible for logged in users.

Date vs. Boolean

El modelado de tablas de bases de datos puede dar lugar rápidamente a redundancias que...

Deja un comentario

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