La historia de un nuevo ataque a la cadena de suministros.
Desde que comencé a aprender a codificar, me ha fascinado el nivel de confianza que ponemos en un comando simple como este:
pip install nombre_paquete
Algunos lenguajes de programación, como Python, vienen con un método fácil, más o menos oficial, para instalar dependencias para sus proyectos. Estos instaladores generalmente están vinculados a repositorios de códigos públicos donde cualquiera puede cargar libremente paquetes de códigos para que otros los usen.
Probablemente ya haya oído hablar de estas herramientas: Node "npm"
y el registro npm, Python "pip
" usa PyPI (Python Package Index), y las gemas de Ruby se pueden encontrar en... bueno, RubyGems.
Al descargar y usar un paquete de cualquiera de estas fuentes, básicamente está confiando en su editor para ejecutar el código en su máquina. Entonces, ¿esta confianza ciega puede ser explotada por actores malintencionados?
Por supuesto que puede.
Ninguno de los servicios de alojamiento de paquetes puede garantizar que todo el código que cargan sus usuarios esté libre de malware. Investigaciones anteriores han demostrado que el typosquatting, un ataque que aprovecha versiones con errores tipográficos de nombres de paquetes populares, puede ser increíblemente efectivo para obtener acceso a PC aleatorias en todo el mundo.
Otras rutas de ataque de cadena de dependencia bien conocidas incluyen el uso de varios métodos para comprometer paquetes existentes o la carga de código malicioso con los nombres de dependencias que ya no existen.
La idea
Mientras intentaba piratear PayPal conmigo durante el verano de 2020, Justin Gardner ( @Rhynorater ) compartió un fragmento interesante del código fuente de Node.js que se encuentra en GitHub.
El código estaba destinado al uso interno de PayPal y, en su package.json
El
archivo parecía contener una combinación de dependencias públicas y
privadas: paquetes públicos de npm, así como nombres de paquetes no
públicos, probablemente alojados internamente por PayPal. Estos nombres
no existían en el registro público de npm en ese momento.
Con la lógica que dicta qué paquete se obtendría de dónde no está claro aquí, surgieron algunas preguntas:
- ¿Qué sucede si se carga un código malicioso en npm con estos nombres? ¿Es posible que algunos de los proyectos internos de PayPal comiencen a usar de forma predeterminada los nuevos paquetes públicos en lugar de los privados?
- ¿Los desarrolladores, o incluso los sistemas automatizados, comenzarán a ejecutar el código dentro de las bibliotecas?
- Si esto funciona, ¿podemos obtener una recompensa por errores?
- ¿Funcionaría este ataque contra otras empresas también?
Sin más preámbulos, comencé a trabajar en un plan para responder a estas preguntas.
La idea era cargar mis propios paquetes de Node "maliciosos" en el registro de npm con todos los nombres no reclamados, que "llamarían a casa" desde cada computadora en la que estuvieran instalados. Si alguno de los paquetes terminara instalándose en servidores propiedad de PayPal, o en cualquier otro lugar, el código dentro de ellos me lo notificaría de inmediato.
En este punto, creo que es importante dejar en claro que cada organización a la que se dirige esta investigación ha otorgado permiso para que se pruebe su seguridad, ya sea a través de programas públicos de recompensas por errores o mediante acuerdos privados. Por favor, no intente este tipo de prueba sin autorización.
“Siempre es DNS”
Afortunadamente,
npm permite que el código arbitrario se ejecute automáticamente al
instalar el paquete, lo que me permite crear fácilmente un paquete Node
que recopila información básica sobre cada máquina en la que está
instalado a través de su "preinstall
" guion.
Para lograr un equilibrio entre la capacidad de identificar una organización en función de los datos y la necesidad de evitar recopilar demasiada información confidencial, decidí registrar solo el nombre de usuario, el nombre de host y la ruta actual de cada instalación única. Junto con las direcciones IP externas, estos fueron suficientes datos para ayudar a los equipos de seguridad a identificar sistemas posiblemente vulnerables en función de mis informes, al tiempo que evitaba que mis pruebas se confundieran con un ataque real.
Ahora queda una cosa: ¿cómo me devuelven esos datos?
Sabiendo que la mayoría de los objetivos posibles estarían muy dentro de redes corporativas bien protegidas, consideré que la exfiltración de DNS era el camino a seguir.
El envío de la información a mi servidor a través del protocolo DNS no era esencial para que la prueba en sí funcionara, pero sí garantizaba que era menos probable que el tráfico se bloqueara o detectara al salir.
Los datos se codificaron en hexadecimal y se usaron como parte de una consulta de DNS, que llegó a mi servidor de nombres autorizado personalizado, ya sea directamente o a través de resolutores intermedios. El servidor se configuró para registrar cada consulta recibida, esencialmente manteniendo un registro de cada máquina donde se descargaron los paquetes.
Cuanto más, mejor
Con el plan básico para el ataque en su lugar, ahora era el momento de descubrir más posibles objetivos.
La primera estrategia fue buscar ecosistemas alternativos para atacar. Así que transfirí el código a Python y Ruby para poder cargar paquetes similares a PyPI (Python Package Index) y RubyGems respectivamente.
Pero podría decirse que la parte más importante de esta prueba fue encontrar tantos nombres de dependencia relevantes como fuera posible.
Unos días completos de búsqueda de nombres de paquetes privados pertenecientes a algunas de las empresas objetivo revelaron que se podían encontrar muchos otros nombres en GitHub, así como en los principales servicios de alojamiento de paquetes, dentro de paquetes internos que se habían publicado accidentalmente, e incluso dentro de publicaciones en varios foros de Internet.
Sin embargo, el mejor lugar para encontrar nombres de paquetes privados resultó ser… dentro de archivos javascript.
Aparentemente, es bastante común para internos "package.json"
archivos,
que contienen los nombres de las dependencias de un proyecto de
javascript, para que se incrusten en los archivos de script públicos
durante su proceso de compilación, exponiendo los nombres de los
paquetes internos. Del mismo modo, las rutas internas filtradas o
"require()
" las
llamadas dentro de estos archivos también pueden contener nombres de
dependencia. Apple, Yelp y Tesla son solo algunos ejemplos de empresas
cuyos nombres internos quedaron expuestos de esta manera.
Durante la segunda mitad de 2023, gracias a la ayuda de @streaak y sus notables habilidades de reconocimiento, pudimos escanear automáticamente millones de dominios que pertenecen a las empresas objetivo y extraer cientos de nombres de paquetes de JavaScript adicionales que aún no se habían reclamado en el registro npm.
Luego cargué mi código para empaquetar los servicios de alojamiento con todos los nombres encontrados y esperé las devoluciones de llamada.
No hay comentarios:
Publicar un comentario