sábado, 1 de julio de 2017

Inyección SQL



Objetivos:
 Conocer el concepto.
 Explicar los Tipos de Inyección:
---Sentencia de Modificación.
---Sentencia de Inyección.
---Conversión de tipo de datos.
 Ejecutar ejemplos prácticos.
 Técnicas contra la Inyección SQL.

NOTA: Usamos como ejemplo la Base de Datos: ORCL, la cual viene por defecto en cualquier versión de ORACLE.
______________________________________________________________________________________
Concepto.
La Inyección SQL es una técnica para explotar maliciosamente aplicaciones que usan datos provistos por el cliente en sentencias SQL, obteniendo así acceso no autorizado a una base de datos con el fin de ver o manipular datos restringidos.[1]

Inyección SQL es un método de infiltración de código intruso que se vale de una vulnerabilidad informática presente en una aplicación en el nivel de validación de las entradas para realizar operaciones sobre una base de datos.[2]

Origen.
El origen de la vulnerabilidad radica en el incorrecto chequeo o filtrado de las variables utilizadas en un programa que contiene, o bien genera, código SQL. Es, de hecho, un error de una clase más general de vulnerabilidades que puede ocurrir en cualquier lenguaje de programación o script que esté embebido dentro de otro.[2]

Técnicas de Inyección.
Las técnicas de inyección SQL son diferentes, pero todas explotan una sola vulnerabilidad: la entrada de cadena no se valida correctamente y se concatena en una sentencia SQL dinámica.[1]

Técnicas:
 Sentencia de Modificación: Implica alterar deliberadamente una sentencia SQL dinámica para que se ejecute de una manera no intencionada por el desarrollador de aplicaciones. Normalmente, el usuario recupera datos no autorizados cambiando la cláusula WHERE de una sentencia SELECT o insertando una cláusula UNION ALL. El ejemplo clásico de esta técnica es omitir la autenticación de contraseña haciendo una cláusula WHERE siempre TRUE.

Ejemplos:
Nota: Este primer SCRIPT es solo para preparar el escenario y así luego ver los ejemplos de Inyección SQL.

DECLARE
    v_table_name    VARCHAR2(30)  :=  'table';
BEGIN
    FOR i IN 1..3 LOOP
      BEGIN
        EXECUTE IMMEDIATE
                        'CREATE TABLE '||v_table_name||i||'
                        (
                          id    NUMBER(4),
                          name  VARCHAR2(30),
                          date_ DATE
                        )
                        ';
        EXCEPTION
          WHEN OTHERS THEN
            NULL;
      END;
    END LOOP;

    FOR i IN 1..3 LOOP
      FOR IN 1..LOOP
        BEGIN
          EXECUTE IMMEDIATE
                          q'{INSERT INTO }'||v_table_name||i||q'{ (id, name, date_)
                           VALUES(}'||j||q'{,'name',SYSDATE+}'||j||q'{)                          }';
        END;
      END LOOP;
    END LOOP;
END;
/*El SCRIPT anterior, crea tres tablas e inserta 3 registros en cada una de ellas. Nota: si los ejecuta varias veces se insertarán 3 registros por cada vez.*/
---
CREATE OR REPLACE PROCEDURE proc_get_info(p_table VARCHAR2, p_id  VARCHAR2) IS
  TYPE  typ_tab IS
        TABLE OF table1%ROWTYPE
          INDEX BY BINARY_INTEGER;
  v_tab_rec   typ_tab;
  v_query     VARCHAR2(500);
BEGIN
    v_query     :=   q'{
                          SELECT
                                id,
                                name,
                                date_
                          FROM  }'||p_table||q'{ 
                          WHERE id  = }'||p_id||q'{
                        }';

    EXECUTE IMMEDIATE v_query
    BULK COLLECT INTO v_tab_rec;

    FOR i IN NVL(v_tab_rec.FIRST,1)..NVL(v_tab_rec.LAST,0) LOOP
      DBMS_OUTPUT.PUT_LINE('Id: '||v_tab_rec(i).id||', Name: '||v_tab_rec(i).name||', Date: '||v_tab_rec(i).date_);
    END LOOP;
END;
/*Ahora creamos el procedimiento: proc_get_info el cual está adecuado para extraer datos de una de las tablas antes creadas (table1, table2, table3). El procedimiento espera el nombre de una de estas tablas y el código (ID) por el cual se hará el filtro. A continuación mostramos porque dicho SCRIPT dinámico es vulnerable a la Inyección SQL.*/
---
SET SERVEROUTPUT ON
BEGIN
    proc_get_info('table1',1);
END;
/*Invocamos el procedimiento pasandole parámetros validos para así ver el funcionamiento esperado. La siguiente imagen muestra el OUTPUT cuando p_id está entre 1 y 3.*/
---OUTPUTS:
SET SERVEROUTPUT ON
BEGIN
    proc_get_info('table1','1 or 1=1');
END;
/*Como notan en el ejemplo, en lugar de pasar un simple valor numérico al parámetro: p_id se le pasó la siguiente cadena: '1 or 1=1' la cual hace que la consulta retorne todos los registros de la tabla en cuestión.*/
---OUTPUT:
SET SERVEROUTPUT ON
BEGIN
    proc_get_info('table1','1 or 1=1
                            UNION ALL 
                            SELECT * FROM table2 
                            UNION ALL 
                            SELECT * FROM table3');

END;
/*En este ejemplo se le saca major provecho a la vulnerabilidad, aparte de mostrar todos los registros de la tabla especificada, también se muestran los de las otras dos tablas.*/
---OUTPUT:

 Sentencia de Inyección: Significa que un usuario agrega una o más sentencias SQL a una sentencia SQL dinámica. Los bloques PL/SQL anónimos son vulnerables a esta técnica.

Ejemplos:
CREATE OR REPLACE PROCEDURE proc_get_info(p_table VARCHAR2, p_id  VARCHAR2) IS
    v_statement     VARCHAR2(2000);
BEGIN
    v_statement     :=   q'{
                            DECLARE
                                TYPE  typ_tab IS
                                      TABLE OF table1%ROWTYPE
                                        INDEX BY BINARY_INTEGER;
                                v_tab_rec   typ_tab;
                            BEGIN
                                SELECT
                                      id,
                                      name,
                                      date_ BULK COLLECT INTO  v_tab_rec
                                FROM  }'||p_table||q'{ 
                                WHERE id  = }'||p_id||q'{;
  
                              FOR i IN NVL(v_tab_rec.FIRST,1)..NVL(v_tab_rec.LAST,0) LOOP
                                DBMS_OUTPUT.PUT_LINE('Id: '||v_tab_rec(i).id||', Name: '||v_tab_rec(i).name||', Date: '||v_tab_rec(i).date_);
                              END LOOP;
                            END;
                          }';

    EXECUTE IMMEDIATE v_statement;
END;
/*Para mostrar el otro tipo de Inyección SQL modificamos el procedimiento proc_get_info. Ahora en lugar de invocar una consulta dinámica, invoca todo un bloque PL/SQL; En el mismo bloque anónimo se imprime el resultado de la consulta. Nota: Ahora este bloque es mas vulnerable aun.*/
---
SET SERVEROUTPUT ON
BEGIN
    proc_get_info('table1','1; DELETE FROM table2');
END;
/*Este ejemplo muestra como es posible anexar otra sentencia dentro del bloque anónimo; Al ejecutar dicho SCRIPT se eliminarían todos los registros de table2.*/
---
SET SERVEROUTPUT ON
BEGIN
    proc_get_info('table1',q'{1; EXECUTE IMMEDIATE 'DROP TABLE table3'}');

END;
/*En este ejemplo el daño es aun mayor; como se puede ver le fue agregada una sentencia EXECUTE que elimina (DROP) la tabla table3.*/

---

 Conversión de tipo de datos: Una técnica de inyección SQL menos conocida utiliza parámetros de sesión NLS para modificar o inyectar sentencias SQL.

Un valor datetime o numérico concatenado en el texto de una sentencia SQL dinámica debe convertirse al tipo de dato VARCHAR2. La conversión puede ser implícita (cuando el valor es un operando del operador de concatenación) o explícita (cuando el valor es el argumento de la función TO_CHAR). Esta conversión de tipo de datos depende de la configuración de NLS de la sesión de base de datos que ejecuta la sentencia SQL dinámica. La conversión de valores de fecha y hora utiliza modelos de formato especificados en los parámetros NLS_DATE_FORMAT, NLS_TIMESTAMP_FORMAT o NLS_TIMESTAMP_TZ_FORMAT, dependiendo del tipo de datos de fecha y hora concreto. La conversión de valores numéricos aplica separadores decimales y de grupo especificados en el parámetro NLS_NUMERIC_CHARACTERS.

______________________________________________________________________________________
Técnicas contra la Inyección SQL.
Si utiliza SQL dinámico en PL/SQL, debe validar el texto de entrada para asegurar que el usuario no introduzca valores invalidados o maliciosos. Para evitar inyecciones SQL puede utilizar las siguientes técnicas:
 Utilizar argumentos/variables tipo Bind.
 Realizar comprobaciones que validen la integridad del dato recibido.
 Utilizar Modelos de formato explícito.

Uso de argumentos/variables tipo Bind.
La manera más efectiva de hacer que su código PL/SQL sea invulnerable a ataques de inyección SQL es usar argumentos Bind. La base de datos utiliza exclusivamente los valores de los argumentos Bind y no interpreta su contenido. (Los argumentos Bind también mejoran el rendimiento).

Realizar Comprobaciones.
Es recomendable que siempre realice comprobaciones de validez en los datos que recibe del usuario y así asegurarse de que son los valores esperados para procesar el requerimiento.
Ejemplos:
1. En una consulta que filtra por un campo tipo NUMBER no debe permitir que el usuario introduzca valores tipo carácter.
2. Si espera el nombre de una tabla de la cual desea eliminar registros debe comprobar que dicha tabla existe, para ello puede consultar el vista de diccionario ALL_TABLES.

En general, puede realizar cualquier tipo de comprobación que crea pertinente para así evitar la introducción de cláusulas o sentencias adicionales.

Uso de Modelos de formato explícito.
Si utiliza valores de fecha y hora que están concatenados en el texto de una sentencia SQL o PL/SQL y no puede pasarlos como variables Bind, debe conviertalos en texto utilizando modelos de formato explícito que sean independientes de los valores de los parámetros NLS de la sesión en ejecución. Asegúrese de que los valores convertidos tengan el formato datetime SQL o de literales numéricos. El uso de modelos de formatos independientes de la configuración regional al construir SQL es recomendable no sólo desde una perspectiva de seguridad, sino también para garantizar que la sentencia SQL dinámica se ejecute correctamente en cualquier entorno.
______________________________________________________________________________________
En el siguiente enlace podrá conocer mas sobre el SQL Dinámico.
______________________________________________________________________________________
Fuentes: https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm#LNPLS01109
https://es.wikipedia.org/wiki/Inyecci%C3%B3n_SQL