Artículo técnico de Investigación por: Dikla Barda, Roman Zaikin y Oded Vanunu
https://research.checkpoint.com/fakesapp-a-vulnerability-in-whatsapp/
Traducción:
A partir de principios de 2018, la aplicación de mensajería propiedad de Facebook, WhatsApp, tiene más de 1.500 millones de usuarios con más de mil millones de grupos y 65 mil millones de mensajes enviados todos los días. Con tanta charla, el potencial de estafas en línea, rumores y noticias falsas es enorme. Entonces, no ayuda si los actores de amenazas tienen un arma adicional en su arsenal para aprovechar la plataforma con sus intenciones maliciosas.
Sin embargo, Check Point Research recientemente reveló nuevas vulnerabilidades en la popular aplicación de mensajería que podría permitir a los actores amenazar interceptar y manipular los mensajes enviados tanto en conversaciones privadas como grupales, dando a los atacantes un inmenso poder para crear y difundir desinformación de fuentes aparentemente confiables.
Nuestro equipo observó tres posibles métodos de ataque que explotan esta vulnerabilidad, todos los cuales implican tácticas de ingeniería social para engañar a los usuarios finales. Un actor de amenaza puede:
- Use la función ‘citar’ en una conversación grupal para cambiar la identidad del remitente, incluso si esa persona no es miembro del grupo.
- Altere el texto de la respuesta de otra persona, esencialmente poniéndole palabras en la boca.
- Envíe un mensaje privado a otro participante del grupo que esté disfrazado como un mensaje público para todos, de modo que cuando la persona objetivo responda, estará visible para todos los participantes de la conversación.
Tras el proceso de divulgación responsable, Check Point Research informó a WhatsApp de sus hallazgos. Desde la perspectiva de Check Point Research, creemos que estas vulnerabilidades son de suma importancia y requieren atención.
Lea a continuación para nuestro análisis técnico completo.
Video de demostración de los ataques en acción
Análisis técnico
Como es bien sabido, WhatsApp encripta cada mensaje, imagen, llamada, video o cualquier otro tipo de contenido que envíe para que solo el destinatario pueda verlo. Además, ni siquiera WhatsApp tiene la capacidad de ver esos mensajes.
Figura 1: Chat encriptado de WhatsApp
Estos procesos de cifrado llamaron nuestra atención y decidimos tratar de invertir el algoritmo de WhatsApp para descifrar los datos. De hecho, después de descifrar la comunicación de WhatsApp encontramos que WhatsApp está usando el » protocolo protobuf2 » para hacerlo.
Al convertir estos datos de protobuf2 en Json, pudimos ver los parámetros reales que se envían y manipularlos para verificar la seguridad de WhatsApp.
El resultado de nuestra investigación es una extensión de traje de Burp y 3 métodos de manipulación .
Sin embargo, para comenzar la manipulación, primero tenemos que obtener la clave privada y pública de nuestra sesión y completarla en nuestra extensión de traje de bursuit.
Si está interesado en una explicación detallada sobre cómo funciona realmente el cifrado entre bastidores, lea el párrafo de encriptación al final de esta publicación de blog.
Accediendo a las llaves
Las claves se pueden obtener desde la fase de generación de claves desde WhatsApp Web antes de que se genere el código QR:
Figura 2: clave pública y privada de la comunicación
Después de tomar estas claves, debemos tomar el parámetro «secreto» que envía el teléfono móvil a la Web de WhatsApp mientras el usuario escanea el código QR:
Figura 3: La clave secreta de WebSocket
Como resultado de esto, nuestra extensión tendrá el siguiente aspecto:
Figura 4: Extensión del Burp del decodificador de WhatsApp
Después de hacer clic en «Conectar», la extensión se conecta al servidor local de la extensión, que realizará todas las tareas requeridas para la extensión.
Manipulando WhatsApp
Al descifrar la comunicación de WhatsApp, pudimos ver todos los parámetros que realmente se envían entre la versión móvil de WhatsApp y la versión web. Esto nos permitió manipularlos y comenzar a buscar problemas de seguridad.
Esto nos permitió realizar una variedad de tipos de ataques, que se describen a continuación.
Ataque 1: cambiar la identidad de un remitente en un chat grupal, incluso si no es miembro del grupo
En este ataque, es posible falsificar un mensaje de respuesta para suplantar a otro miembro del grupo e incluso a un miembro del grupo no existente, por ejemplo, ‘Mickey Mouse’.
Para suplantar a alguien del grupo, todo lo que el atacante necesita es capturar el tráfico cifrado:
Figura 5: Comunicación encriptada de WhatsApp
Una vez que se captura el tráfico, simplemente puede enviarlo a una extensión que luego descifrará el tráfico:
Figura 6: descifrar el mensaje de WhatsApp
utilizando nuestra extensión
Los parámetros interesantes a tener en cuenta aquí son:
- conversación : este es el contenido real que se envía.
- participante : este es el participante que realmente envió el contenido.
- fromMe : este parámetro indica si envío los datos o alguien más en el grupo.
- remoteJid – Este parámetro indica a qué grupo / contacto se envían los datos.
- id : la identificación de los datos. La misma identificación aparecerá en las bases de datos del teléfono.
Y este es el punto donde las cosas interesantes comienzan a suceder …
Por ejemplo, podemos cambiar la conversación a otra cosa. El mensaje con el contenido » ¡Genial! «Enviado por un miembro de un grupo, por ejemplo, podría cambiarse por algo más como:» Voy a morir, en un hospital en este momento «y el parámetro del participante también podría cambiarse a otra persona del grupo:
Figura 7: un mensaje de respuesta falso
Tenga en cuenta que tenemos que cambiar el ID a otra cosa porque ya está enviado y aparece en la base de datos.
Para hacer que todos vean el nuevo mensaje falso, el atacante debe responder al mensaje que falsificó, citando y cambiando ese mensaje («Excelente») para que se envíe a todos los integrantes del grupo.
Como puede ver en la siguiente captura de pantalla, creamos un nuevo grupo donde no se enviaron mensajes anteriores, y al usar el método de arriba, pudimos crear una respuesta falsa.
Figura 8: La conversación original
El parámetro ‘participante’ también puede ser un texto o un número de teléfono de alguien que no está en el grupo, lo que provocaría que todos en el grupo creyeran que realmente se envió desde este participante.
Por ejemplo:
Figura 9: Cambio del contenido del mensaje
mediante nuestra herramienta de depuración
… y el resultado se verá así:
Esto nuevamente se enviaría a todos en el grupo como antes.
Figura 10: Respuesta a un mensaje enviado desde
alguien fuera del grupo
Ataque 2: cambiar la respuesta de un corresponsal para poner palabras en su boca
En este ataque, el atacante puede manipular el chat enviándole un mensaje a él mismo en nombre de la otra persona, como si hubiera venido de ellos. Al hacerlo, sería posible incriminar a una persona o cerrar un trato fraudulento, por ejemplo.
Para falsificar los mensajes, debemos manipular el parámetro ‘ fromMe ‘ en el mensaje, que indica quién envió el mensaje en el chat personal.
Esta vez capturaremos el mensaje saliente de WhatsApp Web antes de que se envíe a nuestro Burp Suite. Para hacerlo, podemos poner un punto de corte en la función aesCbcEncrypt y tomar los datos del parámetro ‘a’:
Figura 11: Manipulación de mensajes de OutGoing
Copiaremos estos datos en nuestra extensión Burp y seleccionaremos la dirección saliente. Al presionar «Descifrar», nuestra extensión descifrará los datos:
Figura 12: descifrado del mensaje saliente
Después de cambiarlo a falso y encriptarlo de nuevo, obtenemos el siguiente resultado:
Figura 13: Cifrado del mensaje saliente
Tenemos que modificar el parámetro ‘a’ en nuestro navegador, y el resultado será una notificación automática con el contenido. De esta manera, incluso es posible falsificar toda la conversación.
Figura 14: Envío de mensajes a mí mismo
en nombre de otra persona.
Toda la conversación se verá así:
Figura 15: Envío de mensajes a mí mismo
en nombre de alguien más
Ataque 3: envíe un mensaje privado en un grupo de chat, pero cuando el destinatario responda, todo el grupo lo verá.
En este ataque, es posible enviar un mensaje en un chat grupal que solo verá una persona específica, aunque si responde a este mensaje, todo el grupo verá su respuesta.
De esta manera, es posible manipular a un determinado miembro del grupo y «tripitarlo» para que revele información al grupo que de otro modo no querría que ellos supieran.
Encontramos este vector de ataque mientras invertimos la aplicación móvil Android. En esta instancia, encontramos que si el atacante manipula un mensaje simple en el grupo, como «Somos el equipo», en realidad encontraremos este mensaje en ‘/data/data/com.whatsapp/databases/msgstore.db’. base de datos – como se ve a continuación.
Figura 16: Envío de un mensaje privado en el chat grupal
Encontraremos este mensaje en la base de datos ‘/data/data/com.whatsapp/databases/msgstore.db’
Luego, si abrimos la conversación en un teléfono móvil usando el cliente sqlite3 y emitimos el siguiente comando:
SELECCIONE * DESDE mensajes;
Veremos los siguientes datos:
Figura 17: Manipulación de la base de datos
Para enviar un mensaje al grupo, pero restringirlo solo a un miembro específico del grupo, tenemos que establecer su número bajo el parámetro ‘ remote_resource ‘ .
El truco aquí es simplemente cambiar el parámetro ‘ key_from_me ‘ de 0 a 1
Una vez hecho esto, ejecutaremos el siguiente comando y actualizaremos key_from_me y los datos:
mensajes de actualización establecidos key_from_me = 1, data = «¡Todos sabemos lo que has hecho!», donde _id = 2493;
El atacante debe cerrar y volver a abrir su WhatsApp para forzar a la aplicación a enviar el nuevo mensaje. Después de hacerlo, el resultado será el siguiente:
Tenga en cuenta que solo la víctima recibió el mensaje?
Si la víctima escribe algo como respuesta, todos en el grupo recibirán su respuesta, pero si responde el mensaje, solo verá el contenido contestado y todos los demás verán el mensaje original …
Explicación del cifrado de WhatsApp
Código fuente: https://github.com/romanzaikin/BurpExtension-WhatsApp-Decryption-CheckPoint
Comencemos con WhatsApp Web. Antes de generar el código QR, WhatsApp Web genera una clave pública y privada que se utiliza para el cifrado y descifrado.
Figura 23: Clave pública y privada de la conversación
Llamemos a nuestra clave privada ‘ priv_key_list’ y a nuestra clave pública ‘ pub_key_list ‘ .
Estas claves se crearon utilizando curve25519_donna utilizando 32 bytes aleatorios.
Figura 24: Curva de proceso de cifrado25519
Para descifrar los datos, comenzaremos a crear un código de descifrado. Esto tomará la clave privada de WhatsApp Web en lugar de los bytes aleatorios porque necesitamos las mismas claves para descifrar los datos:
self.conn_data [ «private_key» ] = curve25519.Private ( «» .join ([chr (x) para x en priv_key_list]))
self.conn_data [ «public_key» ] = self.conn_data [ «private_key» ] .get_public ( )
assert (self.conn_data [ «public_key» ] .serialize () == «» .join ([chr (x) para x en pub_key_list]))
Luego, después de crear el código QR, luego de escanearlo con un teléfono, podemos enviar la siguiente información a la Web de Whatsapp a través de un websocket:
Figura 25: La clave secreta de WebSocket
El parámetro más importante aquí es el secreto que luego pasa a setSharedSecret. Esto dividirá el secreto en partes múltiples y configurará todas las funciones criptográficas que necesitamos para descifrar el tráfico de WhatsApp.
Primero, podemos ver que hay una traducción de String ‘e’ en Array y algunas divisiones que dividen el secreto en dos partes: ‘n’, que son los primeros 32 bytes y ‘a’, que son los caracteres del 64 ° byte hasta el final de la ‘t’.
Figura 26: Obtener el SharedSecret
Y si profundizamos en la función » E.SharedSecret » , podemos ver que usa dos parámetros, los primeros 32 bytes y la clave privada de la generación QR:
Figura 27: Obtener el SharedSecret
Después de esto, podemos actualizar nuestro código python y agregar la siguiente línea:
self.conn_data [ «shared_secret» ] = self.conn_data [ «private_key» ] .get_shared_key (curve25519.Public (self.conn_data [ «secret» ] [: 32]), clave lambda : key)
A continuación tenemos el gastado que es de 80 bytes:
Figura 28: Ampliación de SharedSecret
Al sumergirse podemos ver que la función usa la función HKDF. Así que encontramos la función ‘pyhkdf’ y la usamos en nuestro código para gastar la clave de la misma manera que lo hizo WhatsApp:
shared_expended = self.conn_data [ «shared_secret_ex» ] = HKDF (self.conn_data [ «shared_secret» ], 80)
A continuación tenemos la función de validación de hmac que toma los datos gastados como parámetro ‘e’ y los divide en 3 parámetros:
- i – los primeros 32 bytes de shared_expended
- r – 32 bytes desde el byte 32
- o – 16 bytes desde el byte 64
También está el parámetro, ‘s’, que es una concatenación del parámetro ‘n’ y ‘a’, de la función anterior a la cual forma parte de nuestro secreto.
Figura 29: HmacSha256
Entonces se llamará a la función HmacSha256 con el parámetro ‘r’ y firmará los datos con el parámetro ‘s’, después de eso ‘n’ recibiremos el verificador de hmac que se comparará con ‘r’, que es un corte de ‘t’ de 32 bytes a 64 bytes, y ‘t’ es nuestro secreto en el formato de matriz, como se vio anteriormente.
Figura 30: Comprobación de la validez de los mensajes
En Python se verá así:
check_hmac = HmacSha256 (shared_expended [32:64], self.conn_data [ «secret» ] [: 32] + self.conn_data [ «secret» ] [64:]) if check_hmac! = self.conn_data [ «secret» ] [ 32:64]: elevar ValueError ( “hmac desajuste de error” )
La última función relacionada con el cifrado en este bloque es ‘aesCbcDecrypt’ que usa el parámetro ‘s’ que es una concatenación entre los datos del byte 64 hasta el final de los compartidos expendidos y los datos del byte 64 del secreto, y ‘i’ que son los primeros 32bytes de gastos compartidos.
Figura 31: Obtener la clave AES y la clave MAC
El resultado es la clave descifrada que usaremos más tarde. Entonces, si traducimos el código, se verá así:
keysDecrypted = AESDecrypt (shared_expended [: 32], shared_expended [64:] + self.conn_data [ «secret» ] [64:]) Después del descifrado, tendremos la nueva ‘t’ que es los primeros 32 bytes, que es la clave de cifrado y los siguientes 32 bytes, que es la clave mac:
self.conn_data [ «key» ] [ «aes_key» ] = keysDecrypted [: 32]
self.conn_data [ «key» ] [ «mac_key» ] = keysDecrypted [32:64]
El código completo se verá así:
self.conn_data [ «private_key» ] = curve25519.Private ( «» .join ([chr (x) para x en priv_key_list]))
self.conn_data [ «public_key» ] = self.conn_data [ «private_key» ] .get_public ( )
assert (self.conn_data [ «public_key» ] .serialize () == «» .join ([chr (x) para x en pub_key_list]))
self.conn_data [ «secret» ] = base64.b64decode (ref_dict [ «secret» ])
self.conn_data [ «shared_secret» ] = self.conn_data [ «private_key» ] .get_shared_key (curve25519.Public (self.conn_data [ «secret « ] [: 32]), clave lambda : tecla)
shared_expended = self.conn_data [ «shared_secret_ex» ] = HKDF (self.conn_data [ «shared_secret» ], 80)
check_hmac = HmacSha256 (shared_expended [32:64], self.conn_data [ «secret» ] [: 32] + self.conn_data [ «secret» ] [64:])
if check_hmac! = self.conn_data [ «secret» ] [32:64]:
raise ValueError ( «Error hmac mismatch» )
keysDecrypted = AESDecrypt (shared_expended [: 32], shared_expended [64:] + self.conn_data [ «secret» ] [64:])
self.conn_data [ «key» ] [ «aes_key» ] = keysDecrypted [: 32]
self.conn_data [ «key» ] [ «mac_key» ] = keysDecrypted [32:64]
Entonces, después de tener el código que puede regenerar todos los parámetros de encriptación necesarios, podemos continuar con el proceso de descifrado.
Para hacer eso, comenzamos con la captura de un mensaje:
Figura 32: El mensaje entrante encriptado
Como puede ver, el mensaje se divide en dos partes: la etiqueta y los datos. Usaremos la siguiente función para descifrar el mensaje:
def decrypt_incoming_message (self, message):
message = base64.b64decode (mensaje)
message_parts = message.split ( «,» , 1)
self.message_tag = message_parts [0]
content = message_parts [1]
check_hmac = hmac_sha256 (self.conn_data [ «mac_key» ], content [32:])
if check_hmac! = content [: 32]:
raise ValueError ( «Error hmac mismatch» )
self.decrypted_content = AESDecrypt (self.conn_data [ «aes_key» ], content [32:])
self.decrypted_seralized_content = whastsapp_read (self.decrypted_content, True)
return self.decrypted_seralized_content
Como puede ver, recibimos los datos en formato base64 para copiar los datos Unicode fácilmente, En Burp podemos codificar los datos a base64 simplemente presionando ctrl + b y pasándolos a la función decrypt_incomping_message. Esta función divide la etiqueta del contenido y comprueba si nuestra clave puede descifrar los datos comparando el hmac_sha256 (self.conn_data [» mac_key «], el contenido [32:]) con el contenido [: 32].
Si todo encaja, podemos continuar con el paso de descifrado de AES que utiliza nuestra clave aes y el contenido de 32bytes.
Este contenido contiene primero el IV, que es del tamaño del tamaño del bloque aes, y luego los datos reales:
self.decrypted_content = AESDecrypt (self.conn_data [ «aes_key» ], content [32:])
El resultado de esta función será un protobuf, que se ve así:
Figura 33: El mensaje descifrado con Protobuf
Para traducirlo a json usaremos la función ‘ whatsapp_read ‘ .
Explicación del cifrado de WhatsApp (descifrar el mensaje entrante):
Para descifrar un mensaje, primero tenemos que entender cómo funciona el protocolo de WhatsApp, así que comenzamos depurando la función e.decrypt :
Figura 34: Función ReadNode
Esta función desencadenará readNode que tiene el siguiente código:
Figura 35: Función ReadNode
Traducimos todo el código a python para representar la misma función que se ve así:
Este código primero lee un byte de la secuencia y lo mueve a char_data. A continuación, intenta leer el tamaño de la lista de la secuencia entrante mediante la función read_list_size.
Luego tenemos otro byte que llamaremos token_byte que se pasará a read_string y se verá así:
Figura 36: Función ReadString
Este código usa getToken y pasa nuestro parámetro como una posición en la matriz token:
Figura 37: Función getToken
Este es el primer elemento que WhatsApp envía en la comunicación, luego traducimos todas las funciones en la función readString y continuamos con la depuración:
A continuación puede ver la función ‘readAttributes’ en la función readNode:
Figura 38: función readAttribues
Esta función simplemente sigue leyendo más bytes de la secuencia y los analiza a través de la misma lista de símbolos que vimos antes cuando procesamos el token de «acción», que se verá así:
Entonces, el segundo parámetro que envía WhatsApp es la acción real al messenger donde podemos ver que WhatsApp envió {add: «replay»} lo que significa que llegó un nuevo mensaje.
Básicamente, continuaremos con el código hasta que lleguemos al final de readNode, que nos dará las tres partes del mensaje que se envió:
- alguna ficha
- algunos atributos de tokens
- el mensaje protobuf codificado
Entonces, hasta este punto obtuvimos la primera y la segunda parte fácilmente reescribiendo todas las funciones a Python, lo cual es muy directo.
Figura 39: matriz descifrada
A continuación tenemos que lidiar con el tercer parámetro que es el protobuf y descifrarlo.
Para obtener el protobuf podemos ver el esquema de protobuf implementado por Whatsapp y simplemente copiarlo en un archivo .proto limpio que se puede obtener desde aquí:
Figura 40: protobuf
Los índices también se pueden copiar desde el esquema de protobuf de Whatsapp y compilarse en el archivo de protobuf de Python utilizando:
Entonces podemos traducir el protobuf a json fácilmente mediante el uso de las funciones de python generadas por el protobuf …
… y el resultado se verá así:
Figura 41: Datos descifrados
Después de implementar eso dentro de nuestras extensiones, pudimos descifrar la comunicación:
Figura 42: Uso de nuestra extensión para descifrar los datos
Cifrado de WhatsApp explicado (cifrar mensaje entrante)
El proceso de cifrado es prácticamente el mismo que el cifrado, pero en el orden opuesto, por lo que esta vez invertiremos la función writeNode :
Figura 43: función writeNode
Que se implementa así:
Figura 44: función writeNode
Como puede ver esta vez, ya tenemos el token y los atributos del token que tenemos que traducir a su posición en las listas de tokens, y luego simplemente volver a implementar toda la función de la misma manera que lo hicimos en readNode:
El código es muy directo; primero comprobamos si el nodo que tenemos tiene una longitud de tres. Luego multiplicamos el número de atributos de token por dos y lo pasamos a writeListStart, que escribirá el inicio del carácter de lista y luego el tamaño de la lista (lo mismo que vimos en readNode ):
Después de tener la lista de inicio, entraremos en writeString que realiza lo mismo que readString , ya que puede ver «acción» traducida a diez, que es la posición de «acción» en el índice de tokens, y así sucesivamente:
Figura 45: función writeToken
Traducimos el código y todas las funciones, que se parecen a las siguientes:
luego el código entra en writeAttributes que traducirá los atributos y de ahí en writeChildren que traducirá los datos reales.
Figura 46: función writeChildren
Traducimos esta función que se ve así:
De esta forma, creamos los datos, de modo que nuestro código que descifra y encripta los mensajes se verá así:
Para simplificar el proceso de cifrado, cambiamos la función real writeChildren y añadimos otro tipo de instancia para simplificar el cifrado:
El resultado es el cifrado y el descifrado de los datos entrantes.
Latest posts by Jose Miguel (see all)
- Curso básico de Ciberseguridad: Módulo 4 - 28 diciembre, 2024
- Curso Básico de Ciberseguridad. Módulo 3 - 21 diciembre, 2024
- Episodio 7: Qué es el ‘Scareware’ - 29 noviembre, 2024
- Curso Básico de Ciberseguridad. Módulo 2 - 23 noviembre, 2024
- Episodio 6: Implementar la seguridad en conexiones remotas. - 22 noviembre, 2024
Debe estar conectado para enviar un comentario.