¡Hola! hoy quiero compartir un tema muy importante a la hora de desarrollar nuestras aplicaciones, es el tema de la seguridad y cómo evitar uno de los temas de vulnerabilidad más importante que es la Inyección SQL.

Siendo muy breve, un ataque de inyección SQL es cuando un usuario malintencionado pasa, o inyecta, un valor malicioso en un formulario en una página web. Ese valor se envía de vuelta al servidor donde se usa para modificar el SQL que se ejecuta posteriormente. Dependiendo del tipo de ataque de inyección de SQL, puede hacer que el servidor devuelva datos que de otra forma estarían restringidos, ejecutar un comando DDL que elimine o altere los objetos de la base de datos, o simplemente eliminar datos de una tabla.

Para esta demostración vamos a usar el siguiente código SQL. El código crea un procedimiento muy simple que toma en un solo parámetro de entrada, el nombre del empleado p_ename. Este parámetro de entrada se utilizará al construir el SQL que se ejecutará. Para que este ejemplo funcione, hay que asegurarse de que la tabla de demostración de EMP esté instalada en nuestro esquema.

CREATE OR REPLACE PROCEDURE ejemplo_insql 
( 
p_ename IN VARCHAR2 
) 
AS 
l_sql VARCHAR2(100); 
type emp_t IS TABLE OF emp%ROWTYPE; 
emp_r emp_t := emp_t(); 
BEGIN 
-- Concatenar la consulta SQL incluyendo quotes 
l_sql := 'SELECT * FROM emp WHERE ENAME = ''' || p_ename || '''';
-- Imprimir la consulta SQL a ser ejecutada 
DBMS_OUTPUT.PUT_LINE(l_sql); 
-- Ejecutar la consulta SQL 
EXECUTE IMMEDIATE l_sql BULK COLLECT INTO emp_r; 
-- Recorrer los resultados e imprimir el nombre del empleado 
FOR x IN emp_r.FIRST..emp_r.LAST 
LOOP 
DBMS_OUTPUT.PUT_LINE('Emp: ' || emp_r(x).ename 
|| ' - Dept:' || emp_r(x).deptno); 
END LOOP; 
END ejemplo_insql; 
/

Tomamos este código y lo ejecutamos en el Taller de SQL.

Ahora vamos a llar a este procedimiento para ver cuál es la salida.

BEGIN 
ejemplo_insql(p_ename => 'KING'); 
END; 
/

Estamos llamando al procedimiento y le estamos pasando el nombre del empleado como KING

Como vemos nos devuelve el resultado: Empleado = KING y el departamento = 10.

Pero qué pasa si nosotros en vez de pasarle solo el nombre del empleado le pasamos algo más…

BEGIN
ejemplo_insql (p_ename => ‘KING” OR ”X” = ”X’);
END;

En este caso dentro de la cláusula WHERE le estamos pasando el nombre del empleado KING y agregamos la clausula OR donde indicamos que X = X. Aquí se está cumpliendo la segunda parte del OR ya que el literal X es igual al literal X. Veamos el resultado del procedimiento.

Al cambiar el parámetro de entrada y haber agregado la segunda parte en el WHERE con el OR, se cambió totalmente la lógica de la consulta SQL mostrando ahora todos los empleados de la tabla EMP. la razón por lo que esto es posible es que el SQL se modifica antes de que la base de datos tenga la oportunidad de analizarlo. El análisis es uno de los primeros pasos que se producen cuando la base de datos intenta ejecutar una consulta SQL. Durante esta fase, la base de datos examina la SQL y se asegura que sea sintácticamente correcta y que todos los objetos al que se hace referencia sean accesibles y sean válidos. la base de datos no se da cuenta si le estamos pasando un parámetro que genera una vulnerabilidad, aún no es tan inteligente :), por eso es que nosotros los desarrolladores debemos ser los que nos aseguremos de que eso no suceda.

Entonces, ¿qué debemos hacer nosotros para evitar esto? Es utilizar SIEMPRE en nuestras consultas SQL y PL/SQL, las variables Bind. Y como lo indica su nombre Variables Bind, (variables de vinculación o enlace en español) se evalúan durante la fase de vinculación del procedimiento de una consulta. Durante la fase de enlace cualquier marcador de posición en la instrucción SQL se reemplaza con el valor correspondiente, dado que la estructura del SQL ya se ha procesado  no se puede influir con un ataque de SQL durante la fase de enlace, por lo tanto haremos el siguiente cambio al código del procedimiento.

CREATE OR REPLACE PROCEDURE ejemplo_insqli_arreglado 
( 
p_ename IN VARCHAR2 
) 
AS 
l_sql VARCHAR2(100); 
type emp_t IS TABLE OF emp%ROWTYPE; 
emp_r emp_t := emp_t(); 
BEGIN 
-- Ensamblar la consulta SQL usando variables bind 
l_sql := 'SELECT * FROM emp WHERE ENAME = :ename';
-- Imprimir la consulta SQL a ser ejecutada 
DBMS_OUTPUT.PUT_LINE(l_sql); 
-- Ejecutar la consulta SQL 
EXECUTE IMMEDIATE l_sql BULK COLLECT INTO emp_r USING p_ename; 
-- Recorrer los resultados e imprimir el nombre del empleado 
IF emp_r.COUNT > 0 THEN 
FOR x IN emp_r.FIRST..emp_r.LAST 
LOOP 
DBMS_OUTPUT.PUT_LINE('Emp: ' || emp_r(x).ename 
|| ' - Dept:' || emp_r(x).deptno); 
END LOOP; 
ELSE 
DBMS_OUTPUT.PUT_LINE('No Data Found'); 
END IF; 
END ejemplo_insqli_arreglado; 
/

El primer cambio que vemos en el procedimiento es que ya no estamos concatenando el parámetro sino que le estamos pasando la variable bind :ename. En segundo lugar, cuando se ejecute la línea del EXECUTE IMMEDIATE se va a vincular lo que está en p_ename a la variable l_sql. Y en tercer lugar se agrega una comprobación de que haya algún registro mayor a cero antes de ingresar al bucle de lo contrario el procedimiento va a mostrar de que no hay información o no hay registros coincidentes.

Ejecutamos el nuevo procedimiento. Una vez creado el procedimiento hacemos la llamada pasándole lo mismo que pasamos antes el cual se generó la vulnerabilidad, provocando la inyección SQL.

BEGIN 
ejemplo_insqli_arreglado(p_ename => 'KING'' OR ''X'' = ''X'); 
END; 
/

 

Como vemos ahora no se produce la vulnerabilidad y nos muestra que no hay registros que coincidan con los datos que estamos buscando.

De esta forma, utilizando las variables bind podemos evitar la inyección SQL en nuestras aplicaciones en APEX.

Este ejemplo y muchos otros más ejemplos estan explicados paso a paso en el curso de Seguridad en el siguiente link: Seguridad en Oracle APEX

¡Hasta Pronto!