Búsqueda de Ciberamenazas

Día cero en Chrome y Edge: “Este exploit está siendo explotado activamente”, así que comprueba tus versiones ahora

Ya está disponible la última actualización de Google Chrome, y esta vez la empresa no se ha andado con rodeos sobre uno de los dos parches de seguridad que incluye:

Google es consciente de que existe un exploit para CVE-2023-3079.

No hay paños calientes, como hemos visto a menudo en Google, para decir que la empresa “es consciente de los informes” de un exploit.

Esta vez, es “somos conscientes de ello por nosotros mismos”, lo que se traduce aún más claramente en “sabemos que los delincuentes están abusando de esto mientras hablamos”, dado que el informe del fallo procede directamente del propio Grupo de Investigación de Amenazas de Google.

Como de costumbre, esto implica que Google estaba investigando un ataque activo (no sabemos si contra la propia Google o contra alguna organización externa) en el que se explotó algún agujero de seguridad desconocido hasta entonces de Chrome.

El fallo se describe simplemente como: Confusión de tipo en V8. (Como es comprensible, Google no dice más que eso por el momento).

Como ya hemos explicado antes, un fallo de confusión de tipo se produce cuando suministras  un programa un fragmento de datos que se supone que debe analizar, validar, procesar y actuar de una manera pero luego consigues engañar al programa para que interprete los datos de una forma diferente, no autorizada, no validada y potencialmente peligrosa.

Los peligros de la confusión de tipo

Imagina que estás escribiendo un programa en C. (No importa si sabes C o no, puedes seguirlo de todos modos).

En C, normalmente declaras las variables individualmente, con lo que no solo reservas memoria donde almacenarlas, sino que también señalas al programa cómo se supone que se van a utilizar esas variables.

Por ejemplo

  long long int JulianDayNumber;
  signed char*  CustomerName;

La primera declaración de variable reserva 64 bits para almacenar un valor entero simple que representa el número del día astronómico. (Por si te lo preguntas, esta tarde es JDN 23157 – los Días Julianos empiezan a mediodía, no a medianoche, porque los astrónomos suelen trabajar de noche, siendo la medianoche la mitad de su jornada laboral).

El segundo reserva 64 bits para almacenar una dirección de memoria donde se encuentre la cadena de texto del nombre de un cliente.

Como puedes imaginar, es mejor que no confundas estos dos valores, porque un número que tiene sentido, y es seguro, para utilizarlo como número de día, como 23157, casi seguro que no sería seguro para utilizarlo como dirección de memoria.

Como puedes ver en este volcado de memoria de un programa Windows en ejecución, la dirección de memoria más baja que se asigna para su uso comienza en 0x00370000, que es 3.604.480 en decimal, mucho mayor que cualquier número de día.

Las direcciones de memoria reales que utiliza Windows varían aleatoriamente con el tiempo, para hacer que la disposición de la memoria sea más difícil de adivinar para los ciberdelincuentes, de modo que si ejecutaras el mismo programa, obtendrías valores, pero serían, no obstante, similares:

Y (aunque está fuera de la parte inferior de la imagen de arriba) las direcciones de memoria de la sección de datos de usuario en tiempo de ejecución cuando este programa se ejecutaba de 0x01130000 a 0x01134FFF, que representan el improbable intervalo de fechas del 22 de julio de 44631 al 16 de agosto de 44687.

De hecho, si intentas mezclar esas dos variables, el compilador debería intentar advertirte, por ejemplo así

  JulianDayNumber = CustomerName;
  CustomerName = JulianDayNumber;

  warning: assignment makes integer from pointer without a cast
  warning: assignment makes pointer from integer without a cast

Ahora bien, si alguna vez has programado en C, sabrás que, para mayor comodidad, puedes declarar variables con varias interpretaciones diferentes utilizando la palabra clave union, así:

  union {
    long long int JulianDayNumber;
    signed char*  CustomerName;
  } data;

Ahora puedes hacer referencia exactamente a la misma variable en memoria de dos formas distintas.

Si escribes data.JulianDayNumber, interpretas mágicamente los datos almacenados como un número entero, pero si escribes data.CustomerName le dices al compilador que estás haciendo referencia a una dirección de memoria, aunque estés accediendo a los mismos datos almacenados.

Lo que estás haciendo, más o menos, es admitir ante el compilador que a veces tratarás los datos que tienes como una fecha, y otras veces como una dirección de memoria, y que asumes la responsabilidad de recordar qué interpretación se aplica en cada momento del código.

Puede que decidas tener una segunda variable, conocida como etiqueta (normalmente un número entero) que acompañe a tu unión para llevar la cuenta de con qué tipo de datos estás trabajando en ese momento, por ejemplo:

  struct {
    int tag;
    union {
      long long int JulianDayNumber;
      signed char*  CustomerName;
    } data;
  } value;

Podrías decidir que cuando value.tag se establece en 0, los datos aún no están inicializados para su uso, 1 significa que estás almacenando una fecha, 2 significa que es una dirección de memoria, y cualquier otra cosa denota un error.

Será mejor que no dejes que nadie se meta con esa configuración de value.tag, o tu programa podría acabar comportándose de forma inesperada.

Un ejemplo más preocupante podría ser algo como esto:

  struct {
    int tag;  // 1 = hash, 2 = function pointers
    union {
      unsigned char hash[16];  // either store a random hash
      struct {
        void* openfunc;        // or two carefully-validated
        void* closefunc;       // code pointers to execute later 
      } validate;
    }
  } value;

Ahora, estamos sobrecargando el mismo bloque de memoria para poder utilizarlo unas veces para almacenar un hash de 16 bytes, y otras para almacenar dos punteros de 8 bytes a funciones que nuestro programa invocará más tarde.

Evidentemente, cuando value.tag == 1, estaríamos encantados de dejar que nuestro programa almacenara cualquier cadena de 16 bytes en la memoria asignada a la unión, porque los hashes son pseudoaleatorios, por lo que cualquier colección de bytes es igualmente probable.

Pero cuando value.tag == 2, nuestro código tendría que ser súper cuidadoso para no permitir que el usuario proporcione direcciones de funciones no validadas, no fiables y desconocidas para ejecutarlas después.

Ahora imagina que pudieras enviar un valor a este código mientras la etiqueta estuviera en 1, de modo que no se comprobara ni validara pero más tarde, justo antes de que el programa utilizara realmente el valor almacenado, pudieras engañar al código para que cambiara la etiqueta a 2.

El código aceptaría entonces tus direcciones de función no validadas como “conocidas y ya verificadas como seguras” (aunque no lo fueran), y enviaría confiadamente la ejecución del programa a una ubicación falsa de la memoria que habías elegido furtivamente de antemano.

Y eso es lo que ocurre en un fallo de confusión de tipo, aunque utilizando un ejemplo artificioso y simplificado,

La memoria que sería segura de consumir si se manejara de una forma, se entrega maliciosamente al programa para que la procese de una forma alternativa e insegura.

¿Qué hacer?

Asegúrate de que tienes la última versión de Chrome o Chromium.

Quieres Chrome 114.0.5735.106 o posterior en Mac y Linux, y 114.0.5735.110 o posterior en Windows.

Microsoft Edge, que está basado en Chromium, también se ve afectado por este fallo.

Ahora deberías tener la versión 114.0.1823.41 o posterior de Edge.

Para comprobar tu versión y forzar una actualización si hay alguna que aún no hayas recibido:

  • Google Chrome: Menú de tres puntos () > Ayuda > Información de Google Chrome.
  • Microsoft Edge: Configuración y más (…) > Ayuda y comentarios > Acerca de Microsoft Edge.