Reporte de Vulnerabilidad en la Autenticación de MySQL
Fecha de Publicacion: Septiembre 26, 2000
Advisory ID: CORE-092600
Bugtraq ID: 1826
CVE CAN: Ninguno asignado hasta el momento.
Titulo: Vulnerabilidad en la Autenticación de MySQL
Clase: Error de Diseño
Explotable Remotamente: Si
Explotable Localmente: Si
Descripcion de la Vulnerabilidad:
para prevenir el paso de passwords en plaintext por la red y a la
vez el almacenamiento de las mismas en ese formato. Para eso todas
las versiones MySQL cuentan con un mecanismo de challenge-and-response
(reto y contestación). Pequeñas variaciones de este mecanismo
pueden encontrarse en la versión 3.20, la 3.21 y las subsiguientes.
Lamentablemente este esquema de autenticación es criptografícamente
inseguro. Específicamente, cada vez que un usuario utiliza este esquema
pierde (ej: muestra) cierta información que puede ser utilizada por un
atacante para recuperar la clave de este usuario. Mediante un
procedimiento de nuestro diseño, que se describe en la sección
``Detalles
Técnicos", un atacante que esta ``escuchando'' en la red puede recuperar
la clave del usuario tan solo después de presenciar unas pocas
ejecuciones
de este esquema, y así autenticarse ante la base de datos personificando
a
un usuario valido.
Sistemas/Paquetes Vulnerables:
Todas las versiones de MySQL
Solucion/Informacion de Vendedores/Workaround:
El vendedor fue avisado de los problemas descriptos y sugiere encriptar
el
tráfico entre cliente y server para prevenir este ataque. Para acceder a
más
detalles se lo refiere a
http://www.mysql.com/documentation/mysql/commented/manual.php?section=Securi
ty
Se estan discutiendo planes para implementar una autenticación segura en
las
próximas versiones de MySQL. Advisories adicionales e información sobre
seguridad
en MySQL pueden encontrarse en:
http://www.securityfocus.com/bid/1147
http://www.securityfocus.com/bid/975
http://www.securityfocus.com/bid/926
Vendedor notificado en: 19 de Octubre de 2000
Creditos:
Estas vulnerabilidades fueron encontradas e investigadas por Ariel
"Wata" Waissbein,
Emiliano Kargieman, Carlos Sarraute, Gerardo Richarte y Agustin "Kato"
Azubel de CORE
SDI, Buenos Aires, Argentina.
Este advisory fue creado con la ayuda del SecurityFocus.com
Vulnerability Help Team.
Para mas información o asistencia en la creacion de advisories por favor
envie un mail a
[EMAIL PROTECTED]
Descripcion Tecnica - Exploit/Codigo de Prueba de Concepto:
1. El mecanismo de challenge-and-response:
Se describe el mecanismo de autenticación de MySQL directamente del
fichero
mysql-3.22.32/sql/password.c:
/***********************************************************************
La idea principal es que no se mandan claves entre cliente y server
durante la conección,
y que ninguna clave se guarda en el server en un formato del que pueda
ser decifrada.
MySQL provee a los usuarios de dos primitivas que son usadas para el
proceso de autenticación:
una función de hash y un generador de números (supuestamente)
pseudo-aleatorios. Al comienzo
de cada conección el server genera una tira de números aleatorios (con
un generador que solo
posee el server) que manda al cliente - este es el challenge o reto. El
cliente usa X-OR del
hash de esta tira con el valor del hash de su clave como entrada de la
función generadora de
números aleatorios. Este es el response (o la respuesta) al reto, que el
manda al server, donde
es comparado con el mismo valor que es generado en el server a partir
del hash de la clave del
cliente y el mismo challenge. La clave se guarda (en user.password)
usando la función PASSWORD()
de mysql.
Ejemplo:
update user set password=PASSWORD("hello") where user="test"
Esta instrucción guarda el numero hasheado como un string en el campo de
clave.
**********************************************************************/
Con ese propósito varias funciones y estructuras de datos se han
implementado:
mysql-3.22.32/include/mysql_com.h:
struct rand_struct {
unsigned long seed1,seed2,max_value;
double max_value_dbl;
};
mysql-3.22.32/sql/password.c:
void randominit(struct rand_struct *rand_st,ulong
seed1, ulong seed2)
Inicializa el PRNG, usado en la versión 3.21
y superiores.
static void old_randominit(struct rand_struct
*rand_st,ulong
seed1)
Inicializa el PRNG, usado en versiones
hasta 3.20.
double rnd(struct rand_struct *rand_st)
Provee de un número random de tipo punto
flotante (double)
entre 0 y rand_st->max_value tomado del PRNG.
void hash_password(ulong *result, const char
*password)
Calcula el hash de la clave y lo guarda en
'result'.
void make_scrambled_password(char *to,const char
*password)
Hashea y guarda la clave en 'to' en un
formato leible.
char *scramble(char *to,const char *message,const
char
*password, my_bool old_ver)
Genera un nuevo mensaje basado en el mensaje
y la clave
El mismo procedimiento se hace en el
servidor y el cliente
my_bool check_scramble(const char *scrambled, const
char
*message, ulong *hash_pass, my_bool old_ver)
Chequea si el string genereado por el
mensaje y el hash de la
clave es identico al string recibido en el
servidor
Este es el chequeo para el esquema de reto y respuesta.
La MySQL engine inicializa el generador de números
pseudo-aleatorios (PRNG)
al iniciar el server como sigue:
mysql-3.22.32/sql/mysqld.cc:main()
randominit(&sql_rand,(ulong) start_time,(ulong) start_time/2);
Donde start_time se obtiene usando la
cantidad de segundos desde
0:00 Enero 1, 1970 UTC usando time(3) cuando el server
inicia.
Nuestra primera observación es que el PRNG es
inicializado con una
semilla facil de adivinar. Sin embargo, esta
observación no tiene
relación directa con la vulnerabilidad que
se presenta en este advisory.
Al conectarse un cliente al server un nuevo hilo
(thread) es creado
para manejarla, y un número pseudo-aleatorio es
calculado por el
generador de números pseudo-aleatorios y ese número es
guardado por
estructura de conección. Este proceso se ejecuta en
mysql-3.22.32/sql/mysqld.cc:create_new_thread():
...
(thread_count-delayed_insert_threads > max_used_connections)
max_used_connections=thread_count-delayed_insert_threads;
thd->thread_id=thread_id++;
for (uint i=0; i < 8 ; i++) // Generate password
teststring
thd->scramble[i]= (char) (rnd(&sql_rand)*94+33);
thd->scramble[8]=0;
thd->rand=sql_rand;
threads.append(thd);
/* Start a new thread to handle connection */
...
El intercambio de reto/respuesta se ejecuta y es
chequeado en
mysql-3.22.32/sql/sql_parse.cc:check_connections():
....
memcpy(end,thd->scramble,SCRAMBLE_LENGTH+1);
end+=SCRAMBLE_LENGTH+1;
...
if (net_write_command(net,protocol_version, buff,
(uint)
(end-buff)) ||
(pkt_len=my_net_read(net)) == packet_error ||
pkt_len < 6)
{
inc_host_errors(&thd->remote.sin_addr);
return(ER_HANDSHAKE_ERROR);
}
Aquí el número pseudo-aleatorio ha sido mandado
(junto con
otra información del server) y la respuesta ha sido
leida.
El chequeo de autenticación se ejecuta.
...
char *passwd= strend((char*) net->read_pos+5)+1;
if (passwd[0] && strlen(passwd) != SCRAMBLE_LENGTH)
return ER_HANDSHAKE_ERROR;
thd->master_access=acl_getroot(thd->host, thd->ip,
thd->user, passwd, thd->scramble,
&thd->priv_user,
protocol_version == 9 ||
!(thd->client_capabilities &
CLIENT_LONG_PASSWORD));
thd->password=test(passwd[0]);
...
acl_getroot() en mysql-3.22.32/sql/sql_acl.cc chequea
los permisos para
el nombre de usuario y el host de donde se origina la
concección, y llama
la función check_scramble descripta arriba para verificar la
respuesta
válida al reto ya mandado. Si la respuesta es chequeada como
válida se
dice que el test (de reto y respuesta) ha sido pasado.
2. El problema: Esquema de Autenticación Cripográficamente Débil
La función de hash provista por MySQL tiene una salida de 8 octetos (64
bits) mientras
que el generador de números psedu-aleatorios tiene una salida de 5
octetos (40 bits).
Nótese que según el mecansimo de autenticación arriba descrpito para
personificar un
usuario ante la base de datos solo hace falta el hash de su clave, e.j.
y no la clave
misma. En lo que sigue se explica el motivo por el cual el hash de la
clave de un usuario
puede ser eficientemente calculada a partir de (mirar) unas pocas
ejecuciones del protocolo
de challenge and response entre el server y un mismo usuario. En
particular, introducimos
una debilidad en el esquema de autenticación y deducimos que un ataque
mucho más eficente que
el ataque de fuerza bruta (brute-force attack) es posible.
Primeramente se describe como funciona el generador de números
pseudo-aleatorios. Luego,
procedemos a analizar la seguridad que proporciona. El algoritmo para
hacer estos cálculos
será introducido en la siguiente sección.
Sea n := 2^{30}-1 (aca n es el valor max_value usado en randominit() y
old_randoninit()
respectivamente). Fijese un usuario U. E iniciese un esquema de
challenge and response.
Supongamos que el server ha mandado un reto (challenge) al usuario U.
Nótese que el hash
de la clave del usuario, y el hash de este reto son cada uno de 8
octetos. Denotese por
P1 los 4 primeros (más significativos) octetos del hash de la clave y
por P2 los últimos
4 octetos de este hash. Analogamente, sean C1 y C2 los primeros 4 y
últimos 4 octetos del
hash del challenge. Luego el (programa que interpreta al) generador de
números psuedo-
aleatorios hace los siguiente:
-calcula los valores seed1 := P1^C1 and seed2 := P2^C2
(aqui ^ denota la funcion XOR)
-calcula recursivamente para 1 =< i =< 8, i++
seed1 = seed1+(3*seed2) modulo (n)
seed2 = seed1+seed2+33 modulo (n)
r[i] = floor((seed1/n)*31)+64
(acá floor denota la función suelo floor(x)=max{n entero tq n=<x})
-calcual a partir de los valores precedentes
seed1 = seed1+(3*seed2) modulo (n)
seed2 = seed1+seed2+33 modulo (n)
r[9] = floor((seed1/n)*31)
-devuelve la respuesta
S=(r[1]^r[9] || r[2]^r[9] || ... || r[7]^r[9] || r[8]^r[9])
Es esta respuesta que manda el usuario U al server para autenticarse. El
server, que
tiene guardado el hash de la clave y el challenge repite este
procedimiento para
encontrar la misma respuesta, y autentica correctamente al usuario si
ambas respuestas
son identicas. Sin embargo, veremos que tan solo con un pequeño número
de estas
respuestas, y sus respectivos retos, le permiten a un atacante obtener
al hash de la
clave P1,P2. Luego, es posible por este medio personificar a cualquier
usuario con
la información que viaja por la red entre el server y el cliente
(usuario).
La razón por la cual el proceso de producir respuestas a partir del X-OR
de los hashes
de la calve y el reto -usando la función generadora de números
pseudo-aleatorios- es
que dicho proceso/función puede ser invertida eficentemente. Más
especificamente,
denote por f a la función que toma los valores X e Y como entrada y
devuelve el
valor S=f(X,Y) (e.g., en nuestro caso es X=P1^C1 e Y=P2^C2). Entonces,
mediante un
algoritmo eficiente, es posible recuperar todos los valores X',Y' que
devuelven la misma salida f(X',Y')=S que el par X,Y. Este conjunto es de
un tamanio negligible en
comparación con los 2^{64} valores de posibles hash de clave en el cual
esta contendio.
Aún más, dada una colección de retos y respuestas entre un mismo usuario
y el server,
es posible calcular eficientemente el conjunto de todos los hashes de
claves que pasan
los retos dados.
3. El ataque
En lo que sigue se da una breve descripción del ataque propuesto. Esta
descripción
permitirá a los lectores verificar el hecho de que el esquema de
autenticación de
MySQL que se describe arriba ``pierde" información. Este ataque ha sido
implementado
en Squeak Smalltalk y esta funcionando en este mismo momento. Una
descripción completa
del ataque va más allá de las miras de este advisory y aparecerá como un
white-paper
en el futuro.
El procedimiento que subyace al ataque esta divido en dos partes. En
estas partes se
usa respectivamente alguna de las siguientes herramientas algorítmicas:
El procedimiento 1 es la herramienta mediante la cual se calcula, a
partir de la respuesta
S y el correspondiente hash del reto C1||C2, el conjunto que consiste de
todos los pares
X,Y que van a parar a S via la función f, i.e. en símbolos {(X,Y):
f(X,Y)=S} (aquí por
supuesto es 0 <=X,Y< 2^{32}).
En el ataque, el procedimiento 1 es usado para reducir el número de
posibles hashes de clave
a 2^{33} del valor máximo 2^{64} correspondiente al ataque de fuerza
bruta. Este conjunto es
altamente eficientemente descripto, e.j. representado en menos de 1Kb de
memoria.
De este nuevo y más pequeño conjunto, es posible eliminar eficientemente
candidatos a hash de
clave usando nuevos pares de reto y respuesta mediante el Procedimiento
2. El Procedimiento
2 es la herramienta algorítmica mediante la cual, a partir de un
conjunto SET de candidatos
a hash de clave, y un nuevo par de reto y respuesta (S,C1||C2), se
cacula el subconjuto de
SET que pasa este nuevo (último) reto.
La manera en la cual el Procedimieto 2 se usa en el ataque debería estar
ya clara. Primeramente
se utiliza el Procedimiento 1 para reducir el conjunto de candidatos de
hash de clave del valor
2^{64} a 2^{33} usando tan solo un par de reto y respuesta para un mismo
usuario. El conjunto
resultante contiene todas los hash de claves que pasan estos dos tests.
Supongase ahora que
se tiene un nuevo par de reto y respuesta (S,C1||C2), entonces el
atacante puede utilizar el
Procedimiento 2 para producir el conjunto -más pequeño- de hash de
claves que pasan los tres
retos en cuestión (los dos correspondientes al Procedimiento 1, y el
último que corresponde al
Procedimiento 2). Cabe aclarar que el Procedimiento 2 puede ser
reaplicado por cada nuevo par
de reto y respuesta que es capturado. Con cada aplicación de este
porcedimiento el conjunto de
candidatos a hash de clave se hace cada vez más pequeño. Aún más, la
cardinalidad de estos
conjuntos de candidatos no es solo decreciente, sino que eventualmente
llega al valor 1. En
cuyo caso el elemento remanente es exactamente el hash de la clave.
4. Estadisticas y Conclusiones
En los ejemplos testeados, quedaron aproximadamente 300 cantidatos a
clave después de aplicar
nuestro ataque a 10 pares de reto y respuesta correspondientes a un
mismo usuario. Nótese que
un ataque de fuerza bruta hubieran quedado
2^{64}-300=18,446,744,073,709,551,316 candidatos a
clave. Tomo unos 100 pares de reto y respuesta en reducir esos 300
candidatos a clave a un conjunto de tan solo 2 candidatos (e.j., una
clave falsa y la verdadera).
Finalmente nos tomó unos 300 pares de clave y respuesta para encontrar
la verdadera clave.
De esta manera nos es posible hacer una amplia variedad de ataques
dependiedo de la cantidad
de pares de reto y respuesta que se poseen del usuario que se quiere
personificar. Los dos
casos extremos son cuando se tienen pocos pares de reto y respuesta, y
el caso en el que se
tiene una grán cantidad de tales pares. El segundo caso extremo, aquel
de muchos pares de
de reto y respuesta capturados, es directo: Aplíquese el algoritmo
arriba descripto hasta
obtener el hash de la clave. El primer caso extremo, aquel de unos pocos
pares de reto y
respuesta capturados, es también fácil de llevar a cabo: simplemente
aplíquese nuestro algoritmo
con todos los pares de reto y respuesta capturados, y luego usese
cualquiera de los candidatos
restantes para la autenticación (muchas de estos falsas claves aún pasan
varios nuevos tests!).
Informacion de Copyright:
~~~~~~~~~~~~~~~~~~~~~~~~~
El contenido de esta advertencia de seguridad es copyright (c) 2000 CORE
SDI S.A. y puede ser distribuida libremente si es que no se cobra ningun
tipo de arancel por su distribución y se mencione quien sostenta su
autoría.
==================[ CORE Seguridad de la Informacion S.A. ]=========
--
Sebastián D. Criado