Búsqueda de Ciberamenazas

OpenSSL corrige un fallo grave de robo de datos ¡actualiza ya!

OpenSSL, probablemente la biblioteca de cifrado más conocida del mundo, si no la más utilizada, acaba de publicar una trifecta de actualizaciones de seguridad.

Estos parches cubren las dos versiones actuales de código abierto que la organización pone a disposición de todo el mundo, además de la “antigua” serie de versiones 1.0.2, cuyas actualizaciones solo están disponibles para los clientes que pagan por un soporte premium.

Las versiones que querrás ver después de actualizar son:

  • OpenSSL serie 3.0: la nueva versión será la 0.8.
  • OpenSSL serie 1.1.1: la nueva versión será 1.1t (esa T al final es de Tango).
  • OpenSSL serie 1.0.2: la nueva versión será 0.2zg (Zulu-Golf).

Si te preguntas por qué las versiones más antiguas tienen tres números, más una letra al final, es porque el proyecto OpenSSL solía tener identificadores de versión de cuatro partes, con la letra final actuando como contador que podía admitir 26 subversiones.

Como puedes ver por lo que ha ocurrido con la versión 1.0.2, 26 subversiones han resultado no ser suficientes, dejando un dilema sobre qué hacer después de la versión Z-por-Zulu: volver a Alfa-Alfa, que rompe confusamente el orden alfabético, o seguir con Z-por-Zulu y empezar un ciclo de sub-sub-versiones de A a Z.

Además, como recordarás, la mezcla de dígitos y letras minúsculas fue especialmente confusa cuando apareció la versión 1.1.1l (L-para-Lima).

Naked Security utiliza felizmente un tipo de letra basado en las señales de tráfico de la época de la Bauhaus que aún se utilizan en muchos países, donde los caracteres de la L minúscula son diferentes de las mayúsculas de la I y del dígito 1, totalmente a propósito, pero muchos tipos de letra representan la L minúscula y la I mayúscula de forma idéntica.

Cuando apareció la versión 3, el equipo de OpenSSL decidió adoptar el popular sistema de versionado de tres números X.Y.Z, por lo que la serie de versiones actual es la 3.0 y la subversión es ahora la 8. (La próxima versión, en desarrollo en este momento, será la 3.1.)

Por si te lo estás preguntando, no había una serie normal OpenSSL 2.x , porque ese número de versión ya se había utilizado para otra cosa, del mismo modo que a IPv4 le siguió IPv6, porque v5 había aparecido en otro contexto durante un breve periodo de tiempo, y podría haber causado confusión.

¿Cuál es el problema?

En total hay ocho correcciones de errores numeradas como CVE, y probablemente no te sorprenderá saber que siete de ellas fueron causadas por una mala gestión de la memoria.

Al igual que OpenSSH, sobre el que escribimos a finales de la semana pasada, OpenSSL está escrito en C, y ocuparse de la asignación y desasignación de memoria en los programas en C suele implicar mucho “hazlo tú mismo”.

 

Desgraciadamente, incluso los programadores experimentados pueden olvidarse de hacer coincidir correctamente sus llamadas a malloc() y sus llamadas a free(), o pueden perder la pista de qué búferes de memoria pertenecen a qué partes de su programa.

Los siete fallos relacionados con la memoria son:

  • CVE-2023-0286: Confusión del tipo de dirección X.400 en X.509 GeneralName. Gravedad alta; el fallo afecta a todas las versiones (3.0, 1.0.1 y 1.0.2).
  • CVE-2023-0215: Use-after-free tras BIO_new_NDEF. Gravedad moderada; el error afecta a todas las versiones (3.0, 1.1.1 y 1.0.2).
  • CVE-2022-4450: Doble liberación tras llamar a PEM_read_bio_ex. Gravedad moderada; el error afecta solo a las versiones 3.0 y 1.1.1.
  • CVE-2022-4203: Desbordamiento del búfer de lectura de X.509 Name Constraints. Gravedad moderada; el error afecta solo a la versión 3.0.
  • CVE-2023-0216: Desreferencia de puntero no válida en las funciones d2i_PKCS7. Gravedad moderada; el error afecta solo a la versión 3.0.
  • CVE-2023-021: Desreferencia nula al validar la clave pública DSA. Gravedad moderada; el fallo afecta solo a la versión 3.0.
  • CVE-2023-0401: Desreferencia NULL durante la verificación de datos PKCS7. Gravedad moderada; el fallo afecta solo a la versión 3.0.

Errores de memoria explicados

Una desreferencia NULL se produce cuando se intenta tratar el número 0 como una dirección de memoria.

Esto suele indicar una variable de almacenamiento incorrectamente inicializada, porque el cero nunca se considera un lugar válido para almacenar datos.

De hecho, todos los sistemas operativos modernos etiquetan deliberadamente los primeros miles o más de bytes de memoria como inutilizables, de modo que intentar leer o escribir la llamada “página cero” provoca un error a nivel de hardware, que permite al sistema operativo cerrar el programa infractor.

No hay forma correcta de recuperarse de este tipo de error, porque es imposible adivinar lo que realmente se pretendía.

Como resultado, los programas con errores remotamente activables de este tipo son propensos a ataques de denegación de servicio (DoS), en los que un ciberdelincuente provoca deliberadamente la vulnerabilidad para forzar el fallo del programa, posiblemente una y otra vez.

Una desreferencia de puntero no válida es algo muy similar, pero significa que se intenta utilizar un número que no representa una dirección de memoria como si lo hiciera.

Dado que la dirección de memoria falsa no existe realmente, este tipo de fallo no suele corromper nada: es como intentar estafar a alguien enviando una citación falsa o una factura falsa a una propiedad que no existe.

Pero, al igual que una des-referencia NULL, el efecto secundario (colapsar el programa) podría convertirse en un ataque DoS.

Los desbordamientos del búfer de lectura significan lo que dicen, es decir, acceder a datos más allá de donde se supone que debes hacerlo, por lo que generalmente no pueden explotarse directamente para corromper o tomar el control de un programa en ejecución.

Pero siempre son preocupantes en las aplicaciones de cifrado, porque los datos superfluos a los que un atacante consigue asomarse pueden incluir información descifrada que se supone que no debe ver, o material criptográfico como contraseñas o claves privadas.

Uno de los desbordamientos de lectura más famosos de la historia fue el fallo de OpenSSL conocido como Heartbleed, por el que un cliente podía pedir a un servidor que “rebotara” un mensaje corto para demostrar que seguía vivo -un latido, como se le conocía-, pero podía engañar al receptor para que devolviera hasta 64Kbytes más de datos de los que contenía originalmente el mensaje entrante. Al “sangrar” los datos del servidor una y otra vez, un atacante podía recomponer gradualmente todo tipo de fragmentos de datos que nunca deberían haberse revelado, a veces incluso claves criptográficas.

Un use-after-free significa que devuelves memoria al sistema, que bien puede entregarla a otra parte de tu programa, pero luego sigues confiando en lo que hay en ese bloque de memoria aunque haya cambiado sin que lo sepas.

En teoría, esto podría permitir a un atacante desencadenar un comportamiento aparentemente inocente en otra parte del programa con el objetivo deliberado de provocar un cambio de memoria que desvíe o tome el control de tu código, dado que sigues confiando en una memoria que ya no controlas.

Un double free es similar, aunque esto significa que devuelves al sistema un bloque de memoria que ya devolviste anteriormente, y que por tanto podría haber sido asignado ya en otra parte del programa.

Al igual que con un use-after-free, esto puede dar lugar a que dos partes del programa confíen en el mismo bloque de memoria, sin que cada parte sea consciente de que los datos que espera que estén presentes (y que puede que ya haya validado y, por tanto, esté dispuesto a confiar en ellos inmediatamente) podrían haber sido cambiados malévolamente por la otra parte.

Por último, el error de confusión de tipo es el más grave en este caso.

La confusión de tipos, en pocas palabras, significa que suministras un parámetro al programa con la excusa de que contiene un tipo de datos, pero más tarde engañas al programa para que lo acepte como un tipo de parámetro diferente.

Como ejemplo muy sencillo, imagina que puedes decirle a un horno doméstico “inteligente” que la hora debe ser, digamos, las 13:37 enviándole el valor entero 1337.

El código receptor probablemente comprobaría cuidadosamente que el número estuviera entre 0 y 2359, ambos inclusive, y que el resto al dividirlo por 100 estuviera en el intervalo de 0 a 59, ambos inclusive, para evitar que el reloj se ajustara a una hora no válida.

Pero ahora imagina que posteriormente pudieras persuadir al horno para que utilizara la hora como temperatura.

Habrías eludido disimuladamente la comprobación que habría tenido lugar si hubieras admitido de entrada que estabas suministrando una temperatura (1337 es demasiado calor para un horno de cocina en cualquiera de las escalas comunes que se utilizan actualmente, ya sean K, °C o °F).

Mal uso de las comparaciones de memoria

En los programas en C, la confusión de tipos suele ser especialmente peligrosa porque puedes intercambiar simples números por punteros de memoria, descubriendo así sigilosamente direcciones de memoria que se suponía que eran secretas o, lo que es mucho peor, leyendo o escribiendo en bloques de memoria que se supone que están fuera de los límites.

Como admite el equipo de OpenSSL, respecto al fallo de confusión de tipo de gravedad alta anterior: “Cuando la comprobación de la lista de revocación de certificados está activada, esta vulnerabilidad puede permitir a un atacante pasar punteros arbitrarios a una llamada a memcmp() [comparación de memoria], permitiéndole leer el contenido de la memoria”.

Si puedes desviar uno de los dos bloques de memoria comparados en una memcmp(), entonces, comparando repetidamente un búfer de memoria secreto contra un bloque de memoria de tu elección, puedes averiguar gradualmente lo que hay en el búfer secreto. Por ejemplo: “¿Empieza esta cadena por A?”. Si no, ¿qué tal B? ¿Sí? ¿Qué sigue? ¿Y BA? ¿BB? Y así sucesivamente.

El error de sincronización completa los ocho

El octavo fallo es:

  • CVE-2022-4303: Timing oracle en el descifrado RSA. Gravedad moderada; el fallo afecta a todas las versiones (3.0, 1.0.1 y 1.0.2).

El código criptográfico debe ser especialmente sensible al tiempo que tardan sus diversos cálculos, de modo que un atacante no pueda adivinar de qué cadenas de texto o números se trata sondeando para ver si la velocidad de respuesta indica que se aplica algún tipo de caso “fácil”.

Como ejemplo sencillo, imagina que te piden que multipliques mentalmente por 13 un número determinado.

Es casi seguro que te llevará mucho más tiempo hacerlo que multiplicar el número por 0 (respuesta instantánea: ¡cero!) o por 1 (respuesta instantánea: el mismo número, sin cambios), y bastante más que multiplicar por 10 (poner un cero al final y leer el nuevo número).

En criptografía, tienes que asegurarte de que todas las tareas relacionadas, como buscar datos en la memoria, comparar cadenas de texto, realizar operaciones aritméticas, etc., tarden lo mismo, aunque eso signifique ralentizar los casos “fáciles” en lugar de intentar ahorrar tiempo haciéndolo todo lo más rápido posible.

¿Qué hacer?

Fácil.

Actualiza ya: necesitas alguna o todas las versiones 1.0.2zg (Zulu-Golf), 1.1.1t (Tango) y 3.0.8.

No olvides que, para muchas distribuciones de Linux, necesitarás instalar una actualización del sistema operativo que se aplica a las bibliotecas compartidas que utilizan muchas aplicaciones diferentes, aunque también puede que tengas aplicaciones que traigan sus propias versiones de OpenSSL y también necesiten actualizarse.

Algunas aplicaciones pueden incluso incluir dos versiones diferentes de OpenSSL, y ambas necesitarán parches.

Date prisa, ¡hazlo ya!

Dejar un comentario

Your email address will not be published. Required fields are marked *