Libssh, crónica de un autómata que desconocía su origen

martes, 23 de octubre de 2018

La semana pasada hemos tenido noticia de una vulnerabilidad reseñable de las que debe quedar registro para aprender de nuestros errores. Libssh, una librería que implementa el protocolo de comunicaciones seguras SSH, corregía un error que permitía evadir el control de autenticación con tan solo enviar un mensaje de estado concreto al servidor.

imagen sacada de Twitter
Fuente: https://twitter.com/lolamby/status/1054012572897366016

Antes de nada, debemos hacer una anotación para despejar dudas, puesto que cuando se anunció la existencia de la vulnerabilidad, hubo una buena parte de usuarios que confundieron esta librería con OpenSSH; la implementación SSH probablemente más usada. Más allá de que implementan el mismo protocolo, estas librerías no tienen relación alguna.

No obstante, a pesar de competir con la popular librería que tiene sus orígenes en el proyecto FreeBSD, libssh posee numerosas aplicaciones que la usan y sobre la que construyen tanto clientes como servidores. Un vistazo a vuelapluma por SHODAN nos arroja más de 6000 resultados. Esto sin agregar la cláusula de puerto “port: 22”, ya que hemos hallado bastantes servidores SSH atendiendo peticiones en puertos no estándar, tales como el 2222 y otros. Asumimos, en cualquier caso, que el número resulta interesante para los atacantes.

Libssh comparte funciones entre el rol de cliente y servidor. Entre ellas un autómata para mantener el estado de las sesiones entre estos. Idealmente, un autómata finito o máquina de estados finitos, nos permite conocer en qué estado se encuentra actualmente una operación y, más importante, saber a qué otros estados se puede progresar en base a la recepción de ciertos mensajes o eventos.

El uso de autómatas en este tipo de esquemas no es novedoso, hay literatura de sobra cubriendo las ventajas y desventajas en este sentido, así como de su fidelidad al estándar. El problema en este caso es que no se controlaba correctamente el origen del mensaje SSH2_MSG_USERAUTH_SUCCESS, y esto ocasiona que el autómata pasase de un estado no autenticado a autenticado; habilitando a su vez la apertura de un canal de forma efectiva entre cliente y servidor sin necesidad de usar una credencial válida.

El fallo: no distinguir el origen de los mensajes
En principio, en un estado concreto de la operación de autenticación se espera que el cliente envíe el mensaje SSH2_MSG_USERAUTH_REQUEST. Eso inicia el proceso de autenticación que debería tener lugar posteriormente a la aceptación o no por parte del servidor. Sin embargo, y tal y como se señala en el post de NCC Group, cuando una petición entra, es procesada por la siguiente función:

Función de procesamiento imagen

¿Qué ocurre dentro de esta función (ssh_packet_process), si el mensaje que llega del cliente posee un estado SSH2_MSG_USERAUTH_SUCCESS en vez de SSH2_MSG_USERAUTH_REQUEST? Veamos:

r=cb->callbacks[type - cb->start](session,type,session->in_buffer,cb->user); 

‘callbacks’ es un array de funciones que acepta, entre otros parámetros, una estructura que aglutina los detalles de la sesión iniciada (session). El problema es que si observamos, el valor usado para desplazarse por el array de funciones es “type - cb->start”, siendo “type” el tipo de mensaje USERAUTH.

Por lo tanto, si el cliente efectúa el cambio de mensaje esperado que ya hemos comentado (SUCCESS por REQUEST), la versión vulnerable del código de libssh no procede a identificar si es el cliente o el servidor el que ha enviado el mensaje. Esto, permite invocar desde el cliente la función correspondiente al tratamiento del mensaje SUCCESS, que debería ser exclusiva del servidor. Luego una vez enviado dicho mensaje:

pantalla sucess

Vemos que se cambian los estados de la estructura session que determinan el estado de la misma a SSH_AUTH_STATE_SUCCESS y SSH_SESSION_STATE_AUTHENTICATED y se agrega el flag SSH_SESSION_FLAG_AUTHENTICATED.

El parche: un centinela dentro del autómata
Como hemos podido comprobar, el fallo se aleja de la familia de errores de gestión manual de la memoria (tan abundantes en C y C++, aunque ya menos desde que C++11 lo mejora). Es un fallo especial, puede darse en cualquier lenguaje de programación porque atiende al diseño. Si analizamos el parche integrado para corregir la vulnerabilidad, veremos un filtro de estados que examina cuidadosamente el origen y estado actual del autómata para impedir desplazamientos a estados no permitidos (de hecho, ahora se acerca más a una máquina de estado finito tal y como lo describe la literatura).

Observemos los cambios:


Es decir, si el mensaje a tratar es del tipo SSH2_MSG_USERAUTH_SUCCESS, de entrada se comprobará si tiene origen o no en un servidor. En caso contrario, si proviene de cliente y es dirigido a un servidor, el paquete se marcará como no válido, SSH_PACKET_DENIED y se saldrá del switch-case sin más dilación.

La prueba-exploit-de-concept
Al poco de conocerse la vulnerabilidad comenzaron a salir las pruebas de concepto / exploits. No era muy complicada la explotación: cambiar un mensaje por otro dentro del proceso estándar de autenticación SSH. Así que básicamente la mayoría de código se basa en este proceso:

código de libssh imagen

Como vemos, abrir conexión con un socket SSL, agregar a la petición de conexión SSH el mensaje SUCCESS y, en el caso particular de la prueba de concepto comentada, ejecutar un comando en el servidor vulnerable.

Los afectados, ¿son muchos?
Evidentemente, libssh no es OpenSSH, de implantación mucho más extendida. Pero esos 6000 servidores que veíamos al comienzo no son, como decíamos, un trofeo desdeñable.

Los usuarios de esta librería en una distribución Linux tipo Ubuntu son, por ejemplo:

usuarios de librería en una distribución Linux tipo Ubuntu imagen

Aun siendo un resultado considerable, la mayoría de paquetes hacen uso de libssh como cliente, lo que no les confiere estatus de vulnerable. Es más preocupante el rol que esta librería puede tener en firmware obsoletos de todo tipo de aparataje de red o dispositivos IoT, donde se suele optar por dependencias ligeras para no abultar el peso de la imagen final del sistema. Ahí es donde podría estar el peligro, sobre todo si se trata de hardware sin mantenimiento o fuera de ciclo de vida.

La vulnerabilidad, descubierta por Peter Winter-Smith del NCC Group, tiene asignado el CVE-2018-10933 y ya está parcheada en las versiones 0.8.4 y 0.7.6. Las versiones superiores a la 0.6.0, donde fue introducido el error, son vulnerables y deberían ser actualizadas. Recordemos que una vulnerabilidad en una librería convierte el fallo en transversal, por lo que todas las aplicaciones que hagan uso de esta, deberán ser recopiladas en la mayoría de los casos.

No cabe duda que esto es un nuevo vector dentro del nutrido arsenal tanto de un atacante como de creadores de botnets IoT; principal producto en el que hemos podido constatar que existe una considerable población de servicios que usan libssh. Así que no tardaremos en ver como las "visitas" son anotadas por los registros de eventos de los IDS preguntando por un tal libssh.

David García
Equipo de Innovación y Laboratorio

No hay comentarios:

Publicar un comentario