Reconstrucción del compromiso de GitHub Actions de TJ Actions Changed Files
17 de marzo de 2025
0 minutos de lecturaLa tarde del viernes 14 de marzo de 2025, comenzaron a surgir detalles sobre un grave exploit de seguridad en una popular GitHub Action llamada changed files (tj-actions/changed-files). Cerca de 23.000 repositorios de GitHub usan esta Action como parte de sus flujos de trabajo de CI y DevOps. Permite rastrear qué archivos cambiaron entre ramas y commits.
Un atacante con privilegios de escritura en el repositorio de la Action hizo un commit que provocó que secretos cifrados aparecieran en texto sin formato en los registros de GitHub Actions. Esto podría ocasionar una filtración devastadora en un repositorio público, donde los registros de la Action también son públicos.
Primero, queremos asegurarles a nuestros clientes que Snyk completó una evaluación de nuestra infraestructura y confirmamos que no usamos una versión afectada de la biblioteca vulnerable. Por lo tanto, Snyk está protegido frente a esta vulnerabilidad.
Dicho esto, queremos compartir un análisis detallado de la vulnerabilidad realizado por Snyk, explicar cómo funciona y presentar medidas lógicas para remediarla, que son el foco de este blog.
Al momento de escribir esto, no se sabe por qué el atacante tenía permisos de escritura en el repositorio que aloja el código de esta GitHub Action. Sin embargo, los factores clave que hicieron posible el ataque, además de contar con permisos de escritura, fueron los siguientes:
Cambiar las etiquetas de versión existentes para que apuntaran al commit del ataque
Dejar el commit del ataque huérfano, sin asociarlo a ninguna rama, incluida la rama principal
Estos factores ayudaron a ocultar el ataque. El ataque dependía de realizar una llamada a una red externa para descargar el código malicioso, lo que finalmente hizo que lo detectaran los servicios que monitorean la actividad de red anómala.
Hacia el final de esta publicación, recreo el ataque (de forma inocua) para que entiendas mejor cómo fue posible y cómo protegerte.
Sobre los commits huérfanos de Git
Prueba este ejercicio. Crea un repositorio nuevo en GitHub y clónalo localmente. Crea una rama y súbela a GitHub. Anota el hash del commit. Luego, elimina la rama remota. Aún puedes navegar hasta ese commit, aunque ya no esté asociado a ninguna rama existente.
Estos son los pasos descritos anteriormente:
1. Crea un repositorio en GitHub
2. Crea un repositorio local y agrega el remoto de GitHub que acabas de crear
3. Crea una rama
4. Agrega un commit y súbelo
5. Anota el commit en GitHub

6. Elimina la rama en GitHub

7. Navega a la URL del commit que anotaste arriba

Así se ve una rama huérfana. Es uno de los factores clave que permitieron explotar el flujo de trabajo de la GitHub Action changes-files.
El otro factor clave fue mover las etiquetas de versión dentro del repositorio.
Sobre las etiquetas de versión de GitHub
Por desgracia, a veces los desarrolladores atribuyen propiedades casi mágicas a las etiquetas de versión de GitHub. Si nos dicen que usemos la versión v35 de un lanzamiento en particular, hacemos referencia a esa etiqueta y asumimos que eso es lo que obtendremos. En circunstancias normales, es una suposición razonable, pero las etiquetas de GitHub son solo cadenas prácticas que apuntan a un commit específico; no tienen nada de mágico, incluso si se usa un versionado semántico riguroso. Aquí tienes un ejemplo parcial de un archivo YAML de GitHub Action que hace referencia a la Action changes-files:
El punto clave está en la última línea, donde se hace referencia a una etiqueta específica del repositorio de GitHub Action. El atacante eliminó las etiquetas de versión originales y las reasignó al commit malicioso, que estaba huérfano. El atacante hizo que varias etiquetas de versión existentes apuntaran a su commit malicioso, lo que amplificó el impacto del ataque.
Puedo demostrarlo si continúo con el ejemplo anterior. En mi máquina local, etiquetaré la rama en la que he estado trabajando:
En GitHub, ahora la etiqueta está asociada al commit huérfano. Puedo navegar a:
Entonces veo esto:

Así, el atacante podía hacer que las personas usaran el commit malicioso simplemente actualizando el repositorio del código de la GitHub Action changed-files.
En resumen, un actor malicioso tenía acceso de escritura a un repositorio del que dependían otros 23.000 repositorios. Aunque el atacante ideó una forma ingeniosa de ocultar sus acciones, nunca habría podido llevar a cabo el ataque sin ese acceso de escritura.
Veamos más de cerca qué buscaba el atacante.
Sobre la filtración de secretos
A menudo, se necesitan secretos para que los scripts de compilación interactúen con otros servicios o para proporcionar las claves necesarias para compilar y ejecutar pruebas.
GitHub cuenta con un sofisticado esquema de cifrado para administrar secretos. Puedes configurar un secreto en el nivel del repositorio de Git y acceder a él desde tu script de compilación sin que aparezca nunca en el registro de compilación ni se filtre. En segundo plano, GitHub descifra y usa el secreto como referencia en tu script de compilación. En la práctica, tu archivo YAML de GitHub Action podría incluir una línea como esta:
El resto del script ya puede acceder al secreto sin que este aparezca en el propio script ni en los registros.
El atacante modificó la GitHub Action changed-files de TJ para descargar un script desde una ubicación remota, guardarlo en un archivo local de la máquina virtual donde se ejecutaba GitHub Action y luego ejecutarlo. Esto se hizo en dos pasos. El primero fue el commit malicioso y huérfano, que ya se eliminó. Esta es la parte clave de ese commit de Git:
Si decodificas en Base64 la cadena larga de arriba (como lo hace el script), obtienes:
El gist al que se hacía referencia ya se eliminó, pero aquí está el script de Python que contenía:
El script anterior, ejecutado como root con el comando sudo, inspecciona la memoria de los procesos para encontrar los secretos descifrados y mostrarlos en el registro de Actions. Esto es muy perjudicial, ya que, por diseño, los registros de GitHub Actions en los repositorios públicos de GitHub también son públicos.
Una publicación en Hacker News (que puedes encontrar aquí), publicada el 14 de marzo, indicó que el exploit había sido detectado por StepSecurity, un servicio que monitorea llamadas de red no autorizadas en GitHub Actions, entre otras cosas. El autor y responsable de mantenimiento de la popular GitHub Action agregó un comentario en esa publicación donde explicaba lo ocurrido. Lo primero que señala es que el atacante tenía acceso de escritura al repositorio.
Gracias a la rápida detección del exploit y a la respuesta oportuna de la persona responsable de su mantenimiento, se corrigió y desactivó muy rápido. Sin embargo, se recomienda a quienes usan la GitHub Action changed-files que revisen sus registros desde el 14 de marzo para asegurarse de que no se hayan filtrado secretos en un registro público de Actions.
El exploit en acción
Creé un conjunto de repositorios para demostrar este exploit, configurados de la forma más parecida posible a la configuración del atacante.
El primer repositorio de Git es donde se define la GitHub Action personalizada: tj-changed-files-action-goof.
El archivo index.js es sencillo y se basa principalmente en el tutorial de GitHub para crear acciones:
Espera una entrada llamada who-to-greet y envía la fecha y la hora a la salida.
El otro repositorio solo tiene un archivo: un archivo YAML configurado para ejecutar la GitHub Action personalizada. También tiene un secreto configurado en el repositorio llamado A_SECRET. El repositorio se llama: tj-changed-files-action-exploit-goof. Si revisas su archivo .github/workflows/main.yml, verás lo siguiente:
Hace un par de cosas:
Muestra un mensaje de Hello, world!
Ejecuta la GitHub Action
tj-changed-files-action-goofMuestra la hora de la Action. También usa el secreto del repositorio de la forma esperada en una GitHub Action.
Compara el resultado de A Goof Step en dos ejecuciones distintas. Primero, esta:
Luego, esta:
En ambos casos, se ejecuta snyk-labs/tj-changed-files-action-goof@v1.0 . En la segunda ejecución, puedes ver el resultado de la GitHub Action comprometida. Y si decodificamos dos veces la cadena codificada en Base64, obtenemos:
En ambas ejecuciones, puedes ver que GitHub Actions protege el valor del secreto cuando se hace referencia a él: a_secret: ***. Sin embargo, el código del exploit extrae los valores de los secretos en texto sin formato directamente de la memoria del sistema.
Si revisamos la etiqueta v1.0 del repositorio tj-changed-files-action-goof, veremos una rama huérfana similar a la del exploit original:

Al principio, v1.0 apuntaba a la rama main. Pero, al simular el exploit, simplemente la reasigné a una rama attack, que luego dejé huérfana:
El último detalle engañoso, que es fácil pasar por alto, es ncc build index.js. Esto actualiza dist/index.js, y solo se confirma ese archivo actualizado. El archivo dist/index.js, empaquetado para la web, es lo que realmente ejecuta GitHub Action. Así que, si revisas el archivo index.js en la rama v1.0 (huérfana), no parece que haya cambiado nada.
Aquí está el diff del código webpack de dist/index.js en v1.0, en comparación con la rama main.
Toma medidas para proteger tus GitHub Actions
Estoy seguro de que surgirán más detalles sobre cómo o por qué el atacante tenía acceso de escritura a este popular repositorio de GitHub Action.
Es una práctica común confiar en las etiquetas de Git como referencia a las versiones de las distintas bibliotecas de las que dependemos.
Hay varias formas de haber evitado este exploit en particular.
1. En el caso de las GitHub Actions personalizadas y remotas, usa directamente el hash del commit como referencia
Por ejemplo, podrías haber hecho lo siguiente:
Esto hace referencia directa al hash del commit en lugar de a una etiqueta. No es una práctica común, pero garantizaría que se ejecute la versión de la GitHub Action personalizada que esperas.
2. Usa GitHub Actions personalizadas adicionales para detectar llamadas de red inesperadas. Así fue como StepSecurity detectó el exploit.
Es alentador ver a una comunidad de colaboradores de código abierto, empresas y personas que se moviliza para encontrar y corregir nuevos exploits cuando aparecen, como ocurrió con este.
Snyk ha publicado investigaciones previas y prácticas recomendadas relacionadas con GitHub Actions. Te recomendamos mucho que las consultes y evalúes tus prácticas de DevSecOps y seguridad:
Llamado a la acción: exploración de vulnerabilidades en GitHub Actions
Cómo crear un pipeline de CI/CD seguro con GitHub Actions para tu aplicación Java
Cómo publicar paquetes npm de forma segura con GitHub Actions
Explora el estado de la seguridad del código abierto
Conoce las tendencias y los enfoques actuales sobre el software de código abierto y la seguridad de la cadena de suministro.



