Skip to main content

Análisis posterior de la puerta trasera maliciosa de event-stream

neweracracker suspicious issue

6 de diciembre de 2018

0 minutos de lectura

La semana pasada ocurrió lo inimaginable. Se publicó en npm un paquete malicioso, flatmap-stream, que luego el usuario right9ctrl agregó como dependencia al popular paquete event-stream. Tiempo después, y tras 8 millones de descargas, aplicaciones de toda la web ejecutaban sin saberlo código malicioso en producción. La semana pasada, poco después de que saliera a la luz el incidente, compartimos nuestras primeras impresiones en el blog. Ahora podemos hacer un análisis posterior más profundo, que incluye una cronología de los acontecimientos. Agradecemos a muchas otras personas que también investigaron este problema y, en particular, al usuario de GitHub maths22, quien hizo ingeniería inversa del código malicioso.

¿Qué es el paquete event-stream?

El paquete event-stream es un conjunto de herramientas que ofrece utilidades para crear y administrar flujos. Su autor es Dominic Tarr (~dominictarr en npmjs), y es uno de los 422 paquetes que tiene en npmjs. El paquete event-stream cuenta con un total de 84 versiones, desde la v0.5.2, publicada en 2011, hasta la versión 3.3.4, lanzada hace dos años, con versiones periódicas hasta entonces.

A lo largo del desarrollo de event-stream, recibió contribuciones de 33 personas distintas, aunque la mayoría se hicieron en sus primeros años y desde entonces solo se han revisado cambios menores:

Gráfico que muestra las adiciones semanales en GitHub en verde y las eliminaciones en rojo, con la mayor actividad concentrada alrededor del 12 y 13 de enero.

El proyecto había recibido más de 2000 estrellas, se había bifurcado 139 veces y 62 usuarios de GitHub se habían suscrito a las notificaciones de cambios. Otros 3931 paquetes (sin contar los paquetes con ámbito) usaban el proyecto.

Cronología de los acontecimientos

Esta cronología muestra algunos de los principales hitos de la historia del proyecto y los momentos clave durante el incidente malicioso. A continuación, analizaremos en detalle cada punto de la cronología y otros aspectos.

Cronología de event-stream de Snyk que muestra la creación del paquete en 2011, su mantenimiento, los cambios en las versiones y los informes del incidente de seguridad de noviembre de 2018

Cadena de acontecimientos

Veamos la cadena de acontecimientos que llevó al uso del paquete malicioso flatmap-stream. Investigamos estos hechos a partir de información pública de GitHub, la caché de Google y npm.

31 de julio de 2015: el usuario de GitHub devinus comenta en un issue del proyecto event-stream y pregunta si sería bienvenida una funcionalidad flatmap. El responsable del paquete, dominictarr, responde que sí y que aceptarían una contribución de un usuario:

Debate en GitHub sobre aceptar un parche de flatMap y por qué mapSync usa emit en lugar de queue, con un enlace a pull-stream.

Podemos especular que el usuario malicioso right9ctrl, descubierto posteriormente, pudo haber usado esta información para planear y llevar a cabo un elaborado ataque de ingeniería social contra el proyecto.

5 de agosto de 2018: un usuario identificado como «Antonio Macias» creó y publicó en npm un paquete no malicioso llamado flatmap-stream.

Después, Antonio Macias propuso que el proyecto event-stream usara el paquete flatmap. El usuario de GitHub right9ctrl se puso en contacto con Dominic Tarr y se ofreció a ayudar con el proyecto y a realizar los cambios necesarios para incorporar la funcionalidad flatmap mediante la dependencia flatmap-stream. Dominic aceptó la propuesta de right9ctrl y lo agregó como colaborador del proyecto event-stream en GitHub; además, le otorgó a right9ctrl todos los permisos para publicar el módulo en el ecosistema de npm. Durante el informe del incidente, Dominic confirmó que ya no tenía permisos para publicar el módulo en npm y, por lo tanto, no podía resolver el incidente (por ejemplo, quitando de npm la versión infectada 3.3.6).

Poco después, right9ctrl envió una serie de commits aparentemente inofensivos al repositorio de event-stream en GitHub:

4 de septiembre de 2018: una actualización de dependencia aparentemente inocua.

4 y 5 de septiembre de 2018: se agregaron ejemplos de map y split: 0cc, ee8, c08, 05b

5 de septiembre de 2018: se publicó la versión 3.3.5 de event-stream con 918.

9 de septiembre de 2018: se agregó la dependencia flatmap-stream en e31

9 de septiembre de 2018: se creó una nueva versión menor de event-stream, la 3.3.6, en 599

16 de septiembre de 2018: se eliminó flatmap-stream del código de event-stream en 908 y del árbol de dependencias en 2bd, y se publicó una versión principal, la 4.0.0

20 de septiembre de 2018: right9ctrl agrega más cambios estéticos al código para mejorar las palabras clave del proyecto en 60d, presumiblemente con el fin de mejorar los resultados de búsqueda en el sitio oficial del registro npmjs.com

5 de octubre de 2018: se publicó una nueva versión menor de flatmap-stream, la 0.1.1, que incluía el ataque de inyección en el código fuente minificado. A partir de ese momento, las instalaciones de event-stream también descargarían la nueva versión infectada 0.1.1 de flatmap como dependencia transitiva.

No hay más evidencia de que el usuario right9ctrl haya seguido trabajando en el proyecto event-stream. Su perfil ya fue eliminado de GitHub y npm, aunque todavía se puede consultar en la caché de Google para analizarlo:

Perfil de GitHub de right9ctrl que muestra tres repositorios populares y un mapa de actividad de contribuciones con 22 contribuciones durante el último año.

29 de octubre de 2018: jaydenseric abrió un issue en nodemon para reportar una advertencia de obsolescencia inesperada. Este mensaje concuerda con la recomendación de OpenSSL de usar un algoritmo más moderno en lugar de EVP_BytesToKey. Se recomienda que los desarrolladores deriven por su cuenta una clave y un IV usando crypto.scrypt() y que utilicen crypto.createDecipheriv() para crear el objeto Decipher.

Incidencia de GitHub titulada “Deprecation warning at start #1442”, que describe una advertencia de Nodemon al iniciar Node.js y muestra detalles de la versión y un fragmento de comando.

19 de noviembre de 2018: NewEraCracker abrió un issue en event-stream.

Captura de pantalla de un comentario que señala actualizaciones sospechosas de flatmap-stream en npm, en las versiones 0.1.0, 0.1.1 y 0.1.2.

19 de noviembre de 2018: NewEraCracker abrió un issue en nodemon.

Incidencia de GitHub titulada “¿Subdependencia con puerta trasera? flatmap-stream-0.1.1 y flatmap-stream-0.1.2”, que detalla una cadena de dependencias sospechosa.

20 de noviembre de 2018: FallingSnow sospecha que se trata de un ataque de inyección.

20 de noviembre de 2018: FallingSnow abre el issue en event-stream.

26 de noviembre de 2018: aparece una publicación en HackerNews y el issue llega a las redes sociales.

26 de noviembre de 2018: se elimina el paquete flatmap-stream de npm.

26 de noviembre de 2018: varios usuarios reportan el problema a Snyk mediante el formulario para reportar vulnerabilidades.

26 de noviembre de 2018: Snyk agrega la entrada del paquete malicioso a su base de datos de vulnerabilidades y notifica a todos los proyectos monitoreados.

26 de noviembre de 2018: Danny Grander, de Snyk, reporta el problema al grupo de trabajo de seguridad de la Node.js Foundation.

27 de noviembre de 2018: Snyk publica una entrada de blog sobre el problema.

El objetivo: Copay

Al inspeccionar con más detalle el código de flatmap-stream, vemos que se trató de un ataque dirigido con precisión contra Copay, una plataforma segura de billetera de bitcoin.

El código malicioso de flatmap-stream se descargó millones de veces y se ejecutó muchos millones más. Los atacantes podrían haber hecho innumerables cosas maliciosas, pero su estrategia fue esperar la oportunidad de ejecutarse durante la compilación de la aplicación Copay. Lo consiguieron y el código se incorporó a las versiones 5.0.2 a 5.1.0 de Copay.

El código de descifrado buscaba la clave en una variable de entorno llamada npm_package_description. npm establece esta variable de entorno con la descripción del paquete raíz. Solo se descifraba si la aplicación cliente era la billetera de bitcoin Copay, que usaba la clave para descifrar la carga útil como «A Secure Bitcoin Wallet». maths22 descubrió esto mediante fuerza bruta con distintas descripciones de paquetes npm.

Para averiguarlo, el usuario maths22 probó distintas descripciones de paquetes npm como claves para descifrar la carga útil. Sin embargo, eso no era todo: la segunda carga útil se ejecutaba al ejecutar comandos de compilación específicos, es decir, solo durante la compilación de las aplicaciones para iOS, Android o escritorio.

La tercera y última carga útil es código JavaScript que se inyecta en otra dependencia, concretamente en ./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js. A diferencia de las dos primeras cargas útiles, que se ejecutaban durante la compilación, esta se ejecutaba dentro de la propia aplicación.

El código malicioso robaba bitcoins y las claves privadas de las billeteras si el saldo superaba los 100 bitcoins o los 1000 BHC (Bitcoin Cash). Copay dio a sus usuarios las siguientes recomendaciones:

Los usuarios no deben intentar transferir fondos a billeteras nuevas importando las frases de respaldo de doce palabras de las billeteras afectadas, ya que corresponden a claves privadas potencialmente comprometidas. Primero deben actualizar las billeteras afectadas (5.0.2-5.1.0) y luego enviar todos los fondos desde ellas a una billetera completamente nueva con la versión 5.2.0, usando la función Send Max para iniciar transacciones con todos los fondos.

También se recomendó a los usuarios que «asumieran» que sus claves privadas podrían haberse visto comprometidas y que «de inmediato» transfirieran sus fondos a billeteras nuevas y seguras con la versión 5.2.0.

El análisis posterior de los hechos y del ataque muestra que fue un ataque bien planeado y ejecutado, llevado a cabo por profesionales y que probablemente requirió meses de preparación.

Conclusión

La serie de acontecimientos descritos en este blog nos recuerda una vez más lo frágil que puede ser el modelo de código abierto si no lo cuidamos. Si quienes se benefician de paquetes populares como event-stream aportaran su apoyo, aunque fuera en una proporción pequeña, habría sido fácil evitar que alguien tomara el control del proyecto con fines maliciosos. El paquete event-stream se incluía como dependencia en todo el ecosistema de npm: estaba presente en al menos 3931 paquetes. Entre los más destacados estaban @vue/cli-ui, vscode, nodemon y ps-tree.

El paquete malicioso podría haber pasado inadvertido si el mensaje de obsolescencia no hubiera llevado a Jayden Seric a abrir un issue en el paquete nodemon. De lo contrario, es probable que no se hubiera descubierto durante mucho tiempo.

En Snyk, defendemos firmemente la divulgación responsable y la investigación de seguridad como parte de nuestra cultura. Si descubres una vulnerabilidad que quieras divulgar de forma responsable, nos encantará saberlo a través de nuestro formulario de divulgación responsable.

Envía un reporte.

Empieza con los retos de Capture the Flag

Aprende a resolver retos de Capture the Flag viendo nuestro taller virtual de nivel básico a pedido.