En
estos últimos días ha habido cierto revuelo con la vulnerabilidad en la
autenticación de MySQL y MariaDB (el fork tras la adquisición de Sun por
Oracle). El fallo permite, en algunas versiones, evadir el proceso de
autenticación y obtener acceso a la base de datos.
El 9 de junio, Sergei Golubchik
publicó en la lista de correo "Full Disclosure"
los detalles de un error en algunas versiones de MySQL. Debido a un manejo
incorrecto del valor devuelto por la función "memcmp" era posible que el sistema diera por correcta una
contraseña diferente a la almacenada por el sistema.
Con esta simple prueba de
concepto, era posible detectar un sistema vulnerable:
for i in `seq 1 512`; do echo 'select
@@version;' | mysql -h 127.0.0.1 -u root mysql --password=X 2>/dev/null
&& break; done
Tal como apunta el propio
Golubchik, la vulnerabilidad se producía dependiendo de la librería estándar
que use MySQL en el sistema donde se ha instalado. Por ejemplo la
implementación de "memcmp"
en la libc de BSD no sería vulnerable, al igual que la implementación "inline" que usa el compilador
"gcc". Sin embargo "glibc", la librería presente en la mayoría
de los sistemas Linux, sí lo es.
Veamos cómo se produce la
vulnerabilidad comenzando por el proceso de autenticación de MySQL. La contraseña nunca viaja en texto plano
cuando se efectúa la autenticación con el servidor MySQL.
$ mysql --host=localhost --user=usuario
--password=contraseña
En realidad cuando el cliente
inicia la conexión con el servidor, este le envía una cadena aleatoria. El
cliente la recibe y la usa como semilla para obtener un hash SHA1. Las
operaciones que efectúa el cliente son las siguientes:
- Obtiene el SHA1 de la
contraseña empleada por el usuario (en el parámetro --password).
- Obtiene el SHA1 del sha1
anterior.
- Obtiene el SHA1 del paso
anterior pero usa como semilla el texto enviado por el servidor.
- Sobre este último valor se hace una operación XOR con el sha1 de la contraseña y el resultado se envía al servidor.
Visualmente en pseudocódigo
quedaría así:
enviar(xor(sha1(contraseña),
sha1(text_del_servidor, sha1(sha1(contraseña)))));
Sobre el valor recibido por el
servidor se efectúan las operaciones en orden inverso, obteniéndose el hash de
la contraseña empleada por el usuario. Este hash es comparado usando la función
"memcmp" contra el hash de
la contraseña almacenado en el servidor.
Veamos el código antes de la
corrección, la función donde se produce la llamada a "memcmp" es llamada "check_scramble"
y se encuentra en
"sql/password.c":
La cabecera de la función:
my_bool
check_scramble(const char *scramble_arg, const
char *message,
const uint8 *hash_stage2)
{...
y la línea vulnerable:
...
return
memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE);
}
Según la especificación de la
función "memcmp", esta
compara tantos bytes como indica el tercer parámetro de las direcciones
apuntadas por el primer y segundo parámetros. Esta función irá comparando byte
a byte y en cuanto encuentre una diferencia la devolverá, signo incluido. Si el
primero es menor devolverá la diferencia negativa y si es mayor será positiva.
Finalmente, si los dos bloques de bytes son iguales, devolverá 0.
Como podemos observar, si "memcmp" devolviese 0, la contraseña
es correcta.
De la documentación en
sql/password.c:
RETURN VALUE
0 password is correct
!0 password is invalid
El problema es que "memcmp" no va a devolver '0' o no
'0', sino un valor entero (int). Dicho entero es convertido al valor que
retorna "check_scramble"
que es del tipo "my_bool"
¿Cómo está definido "my_bool"?
"my_bool" está definido en el archivo include/my_global.h como
sigue:
typedef char my_bool
Esto quiere decir el valor
devuelto por "memcmp" será
truncado a un char posibilitando que el resultado finalmente sea un 0
interpretado incorrectamente.
Idealmente ese "memcmp" debería retornar un 0 o 1 y
es lo que el parche que soluciona la vulnerabilidad contempla:
return test(memcmp(hash_stage2,
hash_stage2_reassured, SHA1_HASH_SIZE));
La diferencia es la aplicación de
"test" sobre el resultado devuelto por "memcmp". Dicha macro está definida en include/my_global.h:
#define test(a) ((a) ? 1 : 0)
Ahora simplemente se trata de una
operación lógica. Si hay un valor distinto de 0 devolverá un 1 (o lo que es lo
mismo un "!0") el cual indica que la contraseña no es correcta.
La vulnerabilidad tiene asignado
el CVE-2012-2122 y se consideran vulnerables las versiones de MySQL/MariaDB
hasta la 5.1.61, 5.2.11, 5.3.5, 5.5.22.
Más información:
Security vulnerability in MySQL/MariaDB sql/password.c
HD Moore
String/Array
Comparison
~mysql/mysql-server/5.1

No hay comentarios:
Publicar un comentario