PL/SQL MÁGICO

Inyección SQL en Oracle


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.

El Concepto de Inyección SQL

La Inyección SQL es una vulnerabilidad crítica que permite a un atacante manipular consultas SQL mediante datos maliciosos enviados desde el cliente. Esta técnica puede otorgar acceso no autorizado a la base de datos, exponiendo o alterando información sensible.

En esencia, la inyección SQL aprovecha fallos en la validación de entradas de una aplicación. Al no filtrar correctamente los datos proporcionados por el usuario, el sistema ejecuta instrucciones no previstas, lo que abre la puerta a operaciones ilegítimas como lectura, modificación o eliminación de registros restringidos.

El origen de la vulnerabilidad radica en el manejo inadecuado de las variables utilizadas en un programa que construye o ejecuta sentencias SQL. Cuando no se realiza un correcto chequeo o filtrado de las entradas, se abre la posibilidad de que un atacante inserte código malicioso en la consulta.

Este problema forma parte de una clase más amplia de vulnerabilidades de inyección, que pueden presentarse en cualquier lenguaje de programación o script embebido dentro de otro. En todos los casos, el patrón común es la falta de validación robusta de los datos externos antes de ser procesados por el sistema.

Técnicas de Inyección

Las técnicas de inyección SQL pueden variar en su implementación, pero todas se apoyan en una misma debilidad: la falta de validación adecuada de las cadenas de entrada. Cuando los datos proporcionados por el usuario se concatenan directamente en una sentencia SQL dinámica sin filtros ni controles, el sistema queda expuesto a la ejecución de instrucciones maliciosas.

En otras palabras, el problema no está en la diversidad de métodos, sino en la vulnerabilidad común: el uso inseguro de entradas externas dentro de consultas SQL.


La Sentencia de Modificación

Consiste en alterar deliberadamente una consulta SQL dinámica para que se ejecute de una forma distinta a la prevista por el desarrollador. El objetivo suele ser acceder a información no autorizada o manipular el comportamiento de la aplicación.

Un atacante puede lograrlo modificando la cláusula WHERE de una sentencia SELECT o insertando una cláusula UNION ALL que combine resultados adicionales. El ejemplo clásico es la omisión del proceso de autenticación: se introduce una condición que siempre evalúe como verdadera (WHERE 1=1), permitiendo el acceso sin necesidad de contraseña.

En resumen, esta técnica explota la construcción insegura de consultas dinámicas, donde las entradas del usuario se concatenan sin validación, abriendo la puerta a operaciones no previstas.


Ejemplos

El script a continuación crea dos tablas (TABLA_PRUEBA1 y TABLA_PRUEBA2) y les inserta dos registros a cada usando SQL dinámico.

DECLARE
    v_table_name    VARCHAR2(30)  :=  'TABLA_PRUEBA';
BEGIN
    FOR i IN 1..2 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..2 LOOP
      FOR j IN 1..2 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;

Consulta las tablas creadas:

SELECT  'TABLA_PRUEBA1' tabla, t1.*
FROM    TABLA_PRUEBA1 t1
UNION
SELECT  'TABLA_PRUEBA2', t2.*
FROM    TABLA_PRUEBA2 t2;

Ahora creamos el procedimiento: proc_get_info el cual está adecuado para extraer datos de una de las tablas antes creadas (TABLA_PRUEBA1 y TABLA_PRUEBA2). 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.

CREATE OR REPLACE PROCEDURE proc_get_info(p_table VARCHAR2
                                        , p_id  VARCHAR2) IS
  TYPE  typ_tab IS
        TABLE OF TABLA_PRUEBA1%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;

A continuación invocamos el procedimiento proc_get_info pasandole parámetros validos para así ver el funcionamiento esperado.

SET SERVEROUTPUT ON
BEGIN
    proc_get_info('TABLA_PRUEBA1',1);
END;

El siguiente ejemplo muestra como vulnerar el proceso proc_get_info para que en lugar de mostrar el registro con el código enviado por parámetro muerte todos los registros de la tabla. Esto evidencia cómo el uso de SQL dinámico inseguro permite que un atacante manipule la consulta y obtenga acceso a datos no autorizados.

SET SERVEROUTPUT ON
BEGIN
    proc_get_info('TABLA_PRUEBA1','1 or 1=1');
END;

El siguiente 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 la otra tabla.

SET SERVEROUTPUT ON
BEGIN
    proc_get_info('TABLA_PRUEBA1','1 or 1=1
                            UNION ALL 
                            SELECT * FROM TABLA_PRUEBA2');

END;

La Sentencia de Inyección

Se refiere a una técnica en la que un usuario malicioso agrega una o más instrucciones SQL dentro de una consulta dinámica ya existente.

  • El atacante aprovecha que el código concatena directamente las entradas del usuario en la sentencia SQL.
  • Al hacerlo, puede insertar comandos adicionales como UNION, DROP, UPDATE o incluso otro SELECT, alterando el comportamiento original.
  • Los bloques PL/SQL anónimos son especialmente vulnerables porque suelen ejecutarse sin controles estrictos de validación, y cualquier parámetro malicioso puede modificar la lógica de la consulta.

Esta técnica convierte una consulta legítima en un vehículo para ejecutar código malicioso, lo que permite acceder, modificar o eliminar datos de manera no autorizada.


Ejemplos

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 más vulnerable aun.

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 TABLA_PRUEBA1%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;

El siguiente ejemplo muestra como es posible anexar otra sentencia dentro del bloque anónimo; Al ejecutar dicho SCRIPT se eliminarían todos los registros de TABLA_PRUEBA2.

SET SERVEROUTPUT ON
BEGIN
    proc_get_info('TABLA_PRUEBA1'
                ,'1; DELETE FROM TABLA_PRUEBA2');
END;

En el siguiente ejemplo el daño es aun mayor. Al ejecutar el script la tabla TABLA_PRUEBA1 es eliminada (DROP).

SET SERVEROUTPUT ON
BEGIN
    proc_get_info('TABLA_PRUEBA2'
                ,q'{1; EXECUTE IMMEDIATE 'DROP TABLE TABLA_PRUEBA1'}');
END;

La conversión de tipo de datos

Es una técnica de inyección SQL menos conocida que aprovecha parámetros de sesión NLS (National Language Support) en Oracle.

  • Estos parámetros controlan aspectos como el formato de números, fechas y cadenas.
  • Un atacante puede manipularlos para que el motor interprete los datos de manera distinta, logrando que una entrada aparentemente inocua se convierta en una sentencia SQL inyectada.
  • Por ejemplo, alterando el formato de fecha o número, el valor enviado puede transformarse en código ejecutable dentro de la consulta dinámica.

Esta técnica se basa en explotar la configuración de sesión NLS para modificar cómo se interpretan los datos, convirtiendo conversiones de tipo en un canal de inyección SQL.

Un valor datetime 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_FORMATNLS_TIMESTAMP_FORMAT 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.


Ejemplos:

Supongamos que un procedimiento espera un número (p_id) y construye dinámicamente la sentencia:

SELECT *
FROM tabla 
WHERE id = '||p_id;

Si el atacante modifica el parámetro de sesión NLS, por ejemplo:

ALTER SESSION SET NLS_NUMERIC_CHARACTERS = ',.';

Entonces un valor como 1.1 podría interpretarse de forma distinta y permitir que se inserte código adicional.

Otro caso es cambiando el formato de fecha con:

ALTER SESSION SET NLS_DATE_FORMAT = '" ) OR 1=1 --';

Y luego pasando una fecha como parámetro, el motor interpreta la cadena como parte de la sentencia SQL, convirtiendo la conversión de tipo en un canal de inyección SQL.

En resumen: al manipular los parámetros NLS, un atacante puede transformar conversiones de datos en sentencias ejecutables, logrando acceso no autorizado.


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 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 SQLo 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.


Conclusión

La inyección SQL demuestra cómo una simple concatenación puede comprometer la seguridad completa de una base de datos. Comprender su funcionamiento es esencial para prevenir ataques y garantizar la integridad de la información. El uso de consultas parametrizadas, validación de entradas y bind variables no solo protege el código, sino que también refuerza las buenas prácticas en desarrollo PL/SQL.