Salir de los brokers de mensajes
Adam Goldschmidt
5 de agosto de 2020
0 minutos de lecturaHace poco reporté dos vulnerabilidades en Apache Airflow, una biblioteca de código abierto que permite a los desarrolladores crear, programar y monitorear flujos de trabajo mediante código. Ambas vulnerabilidades permiten que un atacante cambie el alcance y obtenga privilegios en otra máquina, y ambas dependen de que el atacante acceda al broker de mensajes antes de realizar el ataque.
En esta publicación, quiero mostrarte por qué no se puede confiar en los brokers de mensajes y cómo pude explotar Apache Airflow para obtener privilegios en máquinas que deberían estar protegidas. Pero antes de profundizar en el tema, repasemos primero los conceptos básicos de los brokers de mensajes.
¿Qué es un broker de mensajes?
Un broker de mensajes es un software que permite que los servicios se comuniquen entre sí e intercambien información. Puede parecerse a una API, pero normalmente el broker de mensajes hace esto mediante la implementación de una cola en la que distintos servicios pueden escribir o desde la que pueden leer. Esto permite que los servicios se comuniquen de forma asíncrona, aunque estén escritos en distintos lenguajes o implementados en diferentes plataformas.
Los brokers de mensajes pueden servir de puente entre aplicaciones y permitir que los emisores publiquen mensajes sin saber dónde están los destinatarios ni cuántos hay. Como mencionamos antes, esto se logra mediante un componente llamado cola de mensajes, que almacena los mensajes hasta que un servicio consumidor los procesa. Estas colas también permiten programar de forma asíncrona: como las colas se encargan de entregar los mensajes, el emisor puede seguir realizando otras tareas.
Casos de uso de los brokers de mensajes
Los brokers de mensajes se usan ampliamente en el desarrollo de software. Son útiles cuando se necesita una comunicación confiable entre varios servicios, garantizar la entrega de mensajes o contar con funciones asíncronas.
Los casos de uso de los brokers de mensajes son variados. Aquí va mi intento de señalar los más populares:
Procesamiento de pagos: Es importante que los pagos se envíen una sola vez. Procesar estas transacciones mediante brokers de mensajes garantiza que la información de pago no se pierda ni se duplique, y permite confirmar su recepción.
Tareas asíncronas: Es posible desacoplar el procesamiento pesado de una solicitud de usuario en vivo para que la respuesta sea inmediata y el usuario no tenga que esperar.
Enrutar mensajes a uno o varios destinos: Es más sencillo y fácil de mantener publicar mensajes en una única fuente desde la que puedan leer varios servicios.
Ahora que ya sabes qué son los brokers de mensajes y para qué se usan, veamos por qué pueden ser vulnerables y cómo.
Confiar demasiado en los brokers de mensajes
Todos los desarrolladores saben que las bases de datos pueden sufrir ataques. Por lo general, tomamos medidas de seguridad adicionales, como cifrar las contraseñas, para que a los atacantes les cueste más trabajo incluso si logran vulnerar la base de datos.
Bueno, la lógica indica que debería ser exactamente lo mismo con los brokers de mensajes, ¿no? Los datos que se transfieren finalmente llegan a distintas máquinas y deben tratarse con especial cuidado: no solo hay que cifrar la información confidencial, sino también proteger las máquinas que interactúan con ella.
Por desgracia, no es así. Nuestro equipo de seguridad encontró varios ejemplos de uso inseguro de brokers de mensajes en la práctica; uno de ellos es Apache Airflow. Este uso inseguro consiste en confiar demasiado en los brokers de mensajes, por ejemplo, almacenar comandos en ellos y luego ejecutarlos sin sanitizarlos. Esto podría provocar inyecciones de comandos o la ejecución remota de código en las máquinas que se comunican con los brokers.
Explotar Apache Airflow
¿Qué es Apache Airflow?
Como mencionamos brevemente antes, Apache Airflow es un sistema completo de administración de flujos de trabajo. Con Airflow, los flujos de trabajo se diseñan y expresan como DAG (grafos dirigidos acíclicos), donde cada paso del DAG se define como una tarea específica. Es una plataforma basada en código que te permite iterar en tus flujos de trabajo de manera rápida y eficiente.
Airflow incluye un programador de tareas, responsable de programar y ejecutar los DAG. El programador usa un componente llamado ejecutor para ejecutarlos.
Encontrar una vulnerabilidad de día cero en Airflow
Todo comenzó cuando buscaba vulnerabilidades de deserialización en software de código abierto que usa Celery —una cola distribuida de tareas para Python que implementa un broker de mensajes— como dependencia.
Descubrí que Airflow usa Celery (como ejecutor) con pickle de forma predeterminada, y sabía que existe una vulnerabilidad conocida en el módulo pickle de Python, que Celery llegó a usar de forma predeterminada.
A continuación, se muestra un diagrama que ilustra, a grandes rasgos, cómo funciona Airflow con Celery:

Airflow tiene un programador que usa Celery como ejecutor; este, a su vez, almacena las tareas y las ejecuta según lo programado.
Celery usa el broker de mensajes (Redis o RabbitMQ) para almacenar las tareas. Luego, los workers leen las tareas del broker de mensajes y las ejecutan.
Los workers de Airflow Celery deserializan datos pickle almacenados en el broker de mensajes. Esto significa que, si puedo acceder al broker de mensajes, puedo lograr la ejecución remota de código en los workers mediante un ataque de deserialización. A esta vulnerabilidad se le asignó el CVE-2020-11982.
Espera, ¿qué es un ataque de deserialización?
La serialización es el proceso de convertir un objeto en una secuencia de bytes que se puede guardar en un disco o una base de datos, o enviar por flujos de datos. El proceso inverso, que consiste en crear un objeto a partir de una secuencia de bytes, se llama deserialización. La serialización se usa comúnmente para la comunicación (compartir objetos entre varios hosts) y la persistencia (guardar el estado del objeto en un archivo o una base de datos).
Un ataque de deserialización ocurre cuando una aplicación deserializa datos sin verificar adecuadamente que el resultado sea seguro, lo que permite que el atacante controle el estado o el flujo de ejecución. No estoy diciendo que los brokers de mensajes no deban almacenar datos serializados: hay muchos casos de uso en los que es indispensable. Sin embargo, creo que es algo que se debe tener en cuenta al implementar este tipo de diseño y que vale la pena considerar medidas de seguridad adicionales en estos casos.
Si quieres obtener más información sobre los ataques de deserialización, consulta este artículo.
Obligar a Airflow a deserializar mis tareas con pickle
Mi primer paso fue crear una nueva tarea de Airflow y observar su estructura. Agregué un DAG (una colección de tareas), lo asigné a una cola llamada “test”, configuré Redis como broker de Celery e inicié la cola:
airflow worker -q test
Revisé la estructura de los mensajes de Redis y descubrí que se almacenan en una tabla hash de Redis llamada unacked. Los valores guardados en esta tabla hash tienen la siguiente estructura:
Las partes interesantes son los valores body, content-type y content-encoding. El valor body, una vez decodificado, es:
¡Ten en cuenta que los comandos reales se almacenan aquí! Esto será importante para la segunda vulnerabilidad. También encontré un conjunto llamado unacked_index. Supuse que sus elementos eran los mismos valores que las claves de la tabla hash, así que lo comprobé. Primero, recuperé las claves de la tabla hash:
Luego, mostré todos los elementos de unacked_index:
Como puedes ver, efectivamente son los mismos valores, solo que en otro orden. En ese momento, estaba bastante seguro de que unacked_index se usa para almacenar los ID de las tareas que se ejecutarán, y que la tabla hash sirve para asociar el ID de la tarea con la tarea correspondiente. Esto significaba que, para agregar una tarea personalizada, debía agregar un valor arbitrario a unacked_index y luego crear un elemento en la tabla hash con ese mismo valor como clave y una carga maliciosa como valor.
Para llevar a cabo un ataque de deserialización, necesitaba una carga maliciosa. Creé rápidamente un script de Python que generaba una carga que, al deserializarse con pickle, crearía un archivo nuevo llamado malicious:
Puedes leer más sobre cómo explotar pickle en Python aquí. Así que, para hacer que la cola recogiera el valor malicioso y lo deserializara con pickle, tenía que cambiar content-type a application/x-python-serialize (pickle) y content-encoding a binary. Esta es una prueba de concepto para agregar una tarea maliciosa:
Como expliqué antes, estos son los pasos que seguí:
Agregué un valor arbitrario a
unacked_indexcomo ID de tarea.Agregué un nuevo elemento a
unacked: el ID anterior como clave y una carga maliciosa como valor. La carga maliciosa incluía la carga codificada en base64 como valor debody.
La siguiente imagen ilustra el proceso:

Ejecuté la cola de prueba y, ¡voilà! Se creó un archivo nuevo llamado malicious en el worker que ejecutó la cola: un cambio total de alcance. Explicaré las implicaciones de esta vulnerabilidad justo después de revisar la siguiente.
Vulnerabilidad de inyección de comandos
Pasemos a la segunda vulnerabilidad: una inyección de comandos a la que se le asignó el CVE-2020-11981.
Al revisar el código fuente de Airflow, descubrí que los comandos del broker de mensajes se ejecutan sin sanitizarse. Así que, después del típico grito de “¡SÍ!” que suele venir tras encontrar una vulnerabilidad, volví a Redis y descubrí que podía inyectar la siguiente carga en la clave body:
Siguiendo la misma lógica que antes, esta es una prueba de concepto (aquí no es necesario cambiar content-type ni content-encoding, ya que no necesitamos ninguna operación con pickle):
Después, descubrí que también era posible inyectar esta carga en RabbitMQ desde su panel de control. RabbitMQ no usa la misma estructura que Redis, así que basta con usar la lista JSON sin codificarla en base64.
El impacto de esta vulnerabilidad es bastante similar al de la anterior: control total de las máquinas worker. Para explicarlo mejor, imagina que, antes de explotar esta vulnerabilidad, el atacante solo tenía acceso a una máquina: el broker de mensajes. Además, en algunas instalaciones es posible que se puedan inyectar mensajes en el broker mediante una API, sin siquiera acceder a la máquina del broker de mensajes.
Tras obtener el control total de la máquina worker, es posible exponer secretos, provocar una denegación de servicio e incluso acceder a más máquinas de la misma infraestructura. Vulnerabilidades como esta, que permiten a los atacantes ampliar el alcance de sus ataques y sus permisos dentro de una red vulnerada, pueden marcar la diferencia entre un incidente de seguridad menor y una vulneración a gran escala.
Corrección

Si decides almacenar comandos en tu broker de mensajes, una solución podría ser la que se muestra en la imagen anterior. De esta manera, aunque un atacante controle el broker de mensajes, el worker solo ejecutará comandos conocidos y sanitizará cualquier entrada inesperada. Apache optó por este método de corrección porque la lógica del paquete requiere que los comandos se almacenen en el broker de mensajes.
En cuanto a la seguridad de la infraestructura, deberías agregar medidas de seguridad a tu broker de mensajes, como una autenticación adecuada y TLS, para que a los atacantes les resulte más difícil acceder a él.
Conclusión
Estas dos vulnerabilidades muestran cómo pude ejecutar código y comandos en el servidor de colas (o worker) con solo acceder al broker de mensajes. Es importante tener en cuenta que, en su configuración inicial, Redis no requiere contraseña.
El equipo de Apache reconoció rápidamente estas vulnerabilidades y las corrigió. Aunque el equipo de seguridad de Snyk no considera que sean de gravedad muy alta, ya que ambas requieren un acceso inicial a la infraestructura, siguen siendo peligrosas. No es inusual que una vulnerabilidad requiera privilegios iniciales u otras vulnerabilidades (algún tipo de cadena) para poder explotarse.
La principal lección que quiero que te lleves de esta publicación es esta: No confíes ciegamente en tus brokers de mensajes. Piensa en el diseño y la arquitectura de los brokers y en cómo puedes minimizar los riesgos al usarlos.
Empieza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag viendo nuestro taller virtual introductorio a pedido.


