Como hablamos en la segunda parte de esta serie de tutoriales sobre inyección SQL, de que siempre usemos variables bind dentro de APEX cuando usemos SQL o PL/SQL, existe una excepción y es cuando usamos SQL Dinámico.
Las sentencias de SQL Dinámico constan de dos componentes:
- Componente estático
- Componente variable
Los Informes que tienen el origen definido como Cuerpo de la función PL/SQL que devuelve una consulta SQL, lo que hacen esencialmente, es permitir al desarrollador, que arme una consulta SQL y luego lo use como el origen de un informe.
Ese SQL Dinámico también puede ser usado en otras áreas de APEX como por ejemplo en el origen de las listas de valores (LOV) y en las listas en sí mismas.
Veamos este fragmento de código PL/SQL:
DECLARE l_sql VARCHAR2(255); BEGIN -- Iniciar la sentencia SQL l_sql := 'SELECT * FROM emp'; -- Si P1_ITEM se establece en Y, incluya la clausula WHERE IF :P1_ITEM = 'Y' THEN l_sql := l_sql || ' WHERE deptno = 10'; END IF; -- Devuelve el SQL RETURN l_sql; END;
Este fragmento de código puede generar dos consultas SQL diferentes, según el valor del elemento de APEX P1_ITEM. Si P1_ITEM se establece en Y, entonces el SQL devuelto será SELECT * FROM emp WHERE deptno = 10. De lo contrario, el SQL devuelto será SELECT * FROM emp. El informe APEX al que se devuelve este SQL usará cualquiera de esas declaraciones al generar el informe. Tampoco existe un riesgo inherente de inyección de SQL con este ejemplo, porque no se usan variables en la construcción del SQL. Dentro del WHERE no tenemos variables.
El siguiente ejemplo vamos a usar una variable bind dentro del WHERE. Si el usuario ingresa un valor en el elemento P1_DEPTNO, el código incorporará ese valor en la cláusula WHERE de la consulta.
DECLARE l_sql VARCHAR2(255); BEGIN -- Iniciar la sentencia SQL l_sql := 'SELECT * FROM emp'; IF :P1_DEPTNO IS NOT NULL THEN -- Aplicar el filtro si se proporciona un valor l_sql := l_sql || ' WHERE deptno = ' || :P1_DEPTNO; ELSE -- De lo contrario, forzar la consulta para no devolver filas l_sql := l_sql || ' WHERE 1=2'; END IF; -- Imprimir el SQL htp.p(l_sql); -- Devolver el SQL RETURN l_sql; END;
Por ejemplo, ingresamos un valor válido para P1_DEPTNO = 10, entonces nos traerá todos los empleados del departamento 10.
Por otro lado si el usuario ingresa 10 or 1=1 dentro del elemento, sabemos que 1 = 1 y eo es verdadero, entonces, apesar de que estamos utilizando una variable bind nos va a devolver todos los empleados de la tabla porque está ejecuando esta consulta:
SELECT * FROM emp WHERE deptno = 10 or 1=1
Debido a que es una consulta dinámica la base de datos la va a ejecutar dos veces, aquí está el problema.
La primera vez es para concatenar y devolver la consulta SQL y la segunda es para ejecutarla. La primera vez que se ejecuta el valor de P1_DEPTNO, se evalúa, se reemplaza y luego se concatena con el resto de la cadena lo cual es muy peligroso, porque se está reemplazando todo el valor 10 or 1=1 en la consulta y despues se evalúa, es decir, si nosotros pasamos un fragmento de SQl a través de este elemento P1_DEPTNO, simplemente se va a concatenar como parte de la cadena antes de que se analice el bloque de PL/SQL, por lo tanto, aún se puede lograr un ataque de inyección SQL a pesar de estar usando una variable bind.
La solución a esto es concatenar las variables bind de forma correcta!
FORMA CORRECTA
l_sql := l_sql || ' WHERE deptno = :PX_DEPTNO';
FORMA INCORRECTA
l_sql := l_sql || ' WHERE deptno = ' || :PX_DEPTNO;
Debemos asegurarnos de que las variables que son parte de la consulta SQL no se evalúen mientras se concatenan la cadena, sino que se incluyan como parte de la cadena en su forma de sintaxis de variable bind.
De esta forma evitamos una inyección SQL en un contexto de SQL Dinámico en APEX. la recomendación es que, si no tenemos necesidad de usar SQL Dinámico, lo evitemos dentro de lo posible.
Lógicamente como esto es a modo de estudio, no vamos a estar colocando en nuestros códigos de PL/SQL esta porción de código ya que es muy intrusivo:
-- Imprimir el SQL htp.p(l_sql); -- Devolver el SQL RETURN l_sql;
Lo cual nos muestra que se está construyendo.
Tenemos otra forma de visualizar lo que APEX está construyendo, es desde el Depurador.
Hacemos clic en Depurar en la barra de herramientas del Desarrollador y luego ingresamos en el item: 10 or 1 = 1.
Luego vamos a ver la depuración e ingresamos a la última vista. Buscamos donde se construye la SQL y podemos ver el error de que el número no es válido.
Este ejemplo y muchos otros más ejemplos están explicados paso a paso en el curso de Seguridad en el siguiente link: Seguridad en Oracle APEX
¡Será hasta la próxima!