Amenaza de ataque a la cadena de suministro en CodeQL: incidente de seguridad iniciado por una clave secreta pública de GitHub
(praetorian.com)- Si se aprovechaba la breve ventana de validez de un GITHUB_TOKEN expuesto públicamente, podía abrirse una ruta de ataque a la cadena de suministro capaz de derivar en ejecución de código en workflows de GitHub Actions de varios repositorios que usan CodeQL
- El token estaba dentro de un workflow artifact subido por un workflow de depuración fallido del repositorio
github/codeql-action, y era un token de instalación de GitHub App con permisoscontents: write,actions: writeypackages: write - El éxito del ataque dependía de una condición de carrera: descargar y usar el token entre la subida del artifact y el fin del job; según los logs reales, en unos 1.022 segundos, y en observaciones dentro de unos 2 segundos, se logró crear una branch, hacer push de
poc.txty crear un tag - Como la configuración predeterminada de CodeQL ejecuta internamente el tag v3 de
github/codeql-action, y ese tag no era inmutable, si un atacante movía el tag a un commit malicioso, incluso los repositorios que usaban el workflow predeterminado de CodeQL podían verse afectados - GitHub publicó un PR unas 3 horas después del reporte para desactivar la subida de artifacts de depuración, asignó CVE-2025-24362 y afirmó en el advisory que no hay evidencia de compromiso de la plataforma ni de sus sistemas
Ruta de ataque a la cadena de suministro de CodeQL iniciada desde un artifact público
- Un solo secreto público pudo haber derivado en un potencial ataque a la cadena de suministro contra GitHub CodeQL
- Ese secreto era válido solo durante aproximadamente 1.022 segundos cada vez, pero dentro de esa ventana un atacante podía ejecutar los pasos necesarios para correr código dentro de un workflow de GitHub Actions
- El alcance podía llegar tanto a GitHub Cloud como a GitHub Enterprise
- Los posibles daños eran los siguientes
- Filtración de código fuente de repositorios privados que usan CodeQL
- Robo de GitHub Actions secrets dentro del job del workflow de CodeQL
- Ejecución de código en infraestructura interna donde se ejecuta el workflow de CodeQL
- Robo de secrets de workflows que usan GitHub Actions Cache en repositorios que usan CodeQL
- Según el advisory de GitHub, GitHub no encontró evidencia de compromiso de la plataforma ni de sus sistemas
Cómo se encontró el secreto
- Se usó Actions Artifacts Secret Scanner, que descarga artifacts de workflows de GitHub Actions, descomprime recursivamente los archivos y escanea secretos con Nosey Parker
- Esta función se integró en Chariot y se liberó como open source como una función de Gato
- Tras escanear durante un día, se encontró un token en un artifact del repositorio
github/codeql-action- El artifact objetivo era el zip
my-debug-artifactssubido por el workflow “PR Check – Debug artifacts after failure” - Al extraer el archivo interno
my-db-java-partial.zip, se detectó en un crash report un GitHub Token que empezaba conghs_ - Una verificación manual confirmó que era un token de instalación de GitHub App almacenado en un archivo que contenía variables de entorno del GitHub Runner
- El artifact objetivo era el zip
Estructura de conexión entre GitHub Actions, artifacts y CodeQL
- GitHub Actions es una plataforma de CI/CD que ejecuta en runners las tareas definidas en workflows YAML
- Cada workflow run crea automáticamente un GITHUB_TOKEN, que es un token de instalación de GitHub App generado automáticamente
- El runner usa este token para autenticarse ante GitHub y realizar las tareas que requiere el workflow
- Los permisos pueden configurarse a nivel de archivo de workflow, repositorio u organización
- Un artifact de workflow de GitHub Actions es un archivo que el workflow almacena en GitHub Actions para usarlo más adelante
- De forma predeterminada, es accesible para quienes tienen permiso de lectura sobre el repositorio
- Se conserva hasta 90 días
- CodeQL es el motor de análisis de código de GitHub y se usa para realizar análisis estático en repositorios de GitHub para encontrar vulnerabilidades
- Las herramientas de seguridad como CodeQL a veces necesitan acceder a sistemas y datos sensibles, por lo que, si se ven comprometidas, pueden convertirse en una puerta trasera
Permisos del token y condición de carrera
- En la etapa “Setup Job” de los logs del workflow se confirmó que ese GITHUB_TOKEN tenía permisos completos de escritura
- Los permisos especialmente importantes eran los siguientes
contents: write: permite crear branches, crear tags y subir release artifactsactions: write: permite disparar trabajos de Actions y eventosworkflow_dispatchpackages: write: permite subir packages
- Como el token solo era válido mientras se ejecutaba ese workflow job, el ataque requería tres condiciones
- El token debía tener permisos de escritura
- Había que usar la upload artifact API v4 para obtener el artifact antes de que terminara el job
- El tiempo entre la subida del artifact y la finalización del job debía ser suficiente para descargar, descomprimir y usar el token
- CodeQL no usaba directamente la action
actions/upload-artifact, sino que utilizaba manualmente el cliente de upload artifact desde el código fuente, y los comentarios del código indicaban que usaba la versión 4 - Según los logs crudos de GitHub, el intervalo era muy corto
- “Finalizing artifact upload”: 17:22:09.888
- “Cleaning up orphan processes”: 17:22:10.911
- El cálculo daba aproximadamente 1.022 segundos, y por observación el token seguía siendo válido cerca de 1 segundo más después del cleanup, por lo que se consideró una ventana de unos 2 segundos
PoC: ejecución de artifact_racer.py
- Se escribió un script de Python de verificación llamado
artifact_racer.py - El flujo de funcionamiento era el siguiente
- Consultaba continuamente el repositorio
github/codeql-actionpara detectar si se iniciaba el workflow “PR Check – Debug artifacts after failure” - Monitoreaba los artifacts del workflow en ejecución
- Cuando aparecía un artifact, lo descargaba y extraía el GITHUB_TOKEN
- Con el token extraído, creaba una nueva branch
- Hacía push de un archivo vacío llamado
poc.txta esa branch - Creaba un nuevo tag sobre ese commit
- Consultaba continuamente el repositorio
- Como el artifact pesaba alrededor de 21 MB, se consideró que había posibilidades de éxito dentro del tiempo disponible
- Tras tener éxito en un repositorio de prueba, se ejecutó contra el repositorio
github/codeql-action - Unas 2 horas después se ejecutó el workflow, y el script obtuvo el token y logró crear la branch, hacer push del archivo y crear el tag
Mayor alcance por manipulación de tags
- El atacante podía agregar código malicioso a una branch no protegida usando ese token
- De forma más sigilosa, también era posible insertar un pequeño cambio malicioso en una feature branch antes del merge y esperar a que se fusionara
- El permiso para crear tags también era importante
- Si se colocaba el tag
v3en un commit con código malicioso, los usuarios que usaran manualmentecodeql-action@v3podían ejecutar ese código malicioso
- Si se colocaba el tag
- El mayor impacto venía de la configuración predeterminada de CodeQL
- Cuando se activa CodeQL con la configuración predeterminada desde la configuración del repositorio, se ejecuta un workflow especial de GitHub Actions que no aparece en la lista de workflows del repositorio
- Este workflow hace checkout del repositorio, inicializa CodeQL, ejecuta el scan de CodeQL y sube los resultados
- Internamente ejecuta la action del repositorio
github/codeql-actionen el commit al que apunta el tag v3
- En ese momento, el tag
v3no era inmutable y tampoco se usaba el pinning de workflows recomendado por GitHub - Si un atacante movía el tag
v3a un commit malicioso usando el GITHUB_TOKEN comprometido, los repositorios que usaban el workflow predeterminado de CodeQL podían ejecutar la action de CodeQL maliciosa - Como la action de CodeQL hace checkout del código fuente del repositorio objetivo, una action maliciosa podía filtrar el código fuente de repositorios que usan la configuración predeterminada de CodeQL
Ruta de envenenamiento de GitHub Actions Cache
- El GITHUB_TOKEN de la action predeterminada de CodeQL solo tiene permisos de lectura, por lo que solo con la configuración predeterminada no era posible realizar de inmediato operaciones de escritura en el repositorio, instalar una puerta trasera en un release ni robar secrets mediante
workflow_dispatch - En cambio, la action predeterminada de CodeQL se ejecuta en la main branch del repositorio
- La main branch de un repositorio de GitHub puede escribir cache entries que se usan en todo el repositorio, lo que abre una oportunidad de envenenamiento de GitHub Actions Cache
- GitHub Actions Cache Poisoning es una técnica tratada en el artículo de Adnan Khan, y Cacheract es malware que deja persistencia en el build pipeline mediante cache poisoning
- Si un atacante desplegaba Cacheract desde el workflow de CodeQL, podía darse el siguiente flujo
- Predicción de cache entries
- Sobrescritura de esa entry con una action maliciosa
- Obtención de ejecución de código en workflows que usan
actions/cache - Robo de los GitHub Actions secrets de ese workflow y de un GITHUB_TOKEN con mayores privilegios
- Incluso si se descubría la action de CodeQL maliciosa y se corregía la vulnerabilidad, Cacheract podía seguir realizando cache poisoning
- Como ejemplos de repositorios importantes que usan CodeQL junto con
actions/cachese identificaron Homebrew, Angular y Grafana
CVE-2025-24362 y corrección
- Como resultado de esta divulgación se asignó CVE-2025-24362
- El GITHUB_TOKEN público estaba dentro de un debug artifact subido por CodeQL Action tras un workflow de code scanning fallido
- El repositorio CodeQL Actions estaba provocando fallos de forma intencional, pero si otros usuarios de CodeQL Actions tenían fallos similares, también podrían haber expuesto sus propios secrets como variables de entorno del workflow
- El problema se corrigió en CodeQL Action 3.28.3
- El mayor impacto potencial no estaba tanto en el CVE en sí, sino en la ruta para explotar la vulnerabilidad contra el repositorio CodeQL Actions y realizar un ataque a la cadena de suministro contra usuarios de CodeQL
Respuesta de GitHub y mitigaciones recomendadas
- El cronograma de respuesta de GitHub fue el siguiente
- 22 de enero de 2025, 15:13 UTC: se envió el reporte a GitHub
- 22 de enero de 2025, 17:48 UTC: GitHub confirmó la recepción
- 22 de enero de 2025, 18:28 UTC: GitHub confirmó la vulnerabilidad, desactivó temporalmente el workflow “PR Check – Debug artifacts after failure” y envió un PR para desactivar la subida de artifacts de depuración
- 24 de enero de 2025: GitHub asignó CVE-2025-24362 y publicó el security advisory
- Las medidas para reducir el riesgo de exposición de secrets en artifacts de workflows de GitHub Actions son las siguientes
- Subir como workflow artifact solo archivos o directorios específicos
- Evitar subir artifacts que incluyan variables de entorno,
.git/configo archivos del directorio<path_to_runner_dir>/_work/_temp/del runner - Limitar los permisos de GITHUB_TOKEN a read-only
- Ejecutar un escaneo de secrets antes de subir artifacts
1 comentarios
Comentarios de Hacker News
Si GitHub hubiera impulsado antes las acciones inmutables, el impacto de esto habría sido mucho menor.
Sigo repitiendo que esta función bloquearía más del 70% de la superficie de ataque actual de GitHub Actions. Viendo que hay incidentes todas las semanas, creo que ya es hora de lanzarla.
[1] https://github.com/features/preview/immutable-actions
No hay explicación de por qué este token temporal tenía permisos para crear nuevos despliegues y generar attestations de artefactos.
Dijeron que desactivaron los logs de depuración como corrección, pero no respondieron si ajustaron los permisos del token temporal de forma más adecuada para el motor de análisis de código.
En el pasado, el valor predeterminado era solo la configuración permisiva, así que lamentablemente esta configuración se usa con frecuencia en organizaciones y repositorios antiguos.
Al crear un repositorio nuevo, hereda el valor predeterminado de la organización superior, por lo que, si nadie lo cambia, este valor inseguro sigue quedando. No hay una configuración global de usuario, así que los nuevos repositorios de propiedad personal usan el valor predeterminado restrictivo. Tengo entendido que las organizaciones recién creadas también usan mejores valores predeterminados.
[0]: https://docs.github.com/en/actions/security-for-github-actio...
Cada vez estoy más convencido de que CI y CD deberían ser entornos completamente separados. Que CI se vea comprometido no debería implicar que se filtren tokens relacionados con CD.
Por ejemplo, exigir que se cumpla una condición como
"sub":"repo:octo-org/octo-repo:environment:prod"[1], y si se quiere endurecer más el sistema, se pueden agregar otros [claims divertidos][].1: https://docs.github.com/en/actions/security-for-github-actio...
fun claims: https://github.com/github/actions-oidc-debugger#readme
El tiempo de respuesta no fue ninguna broma. La respuesta de GitHub fue bastante impresionante.
Como alguien con el apellido Prater, y dado que este nombre proviene de Praetorian, realmente me gustaría tener praetorian.com.
Usar GitHub Actions públicas es buscarse problemas, y más si no se analizan los procedimientos del workflow.
Mejor autohospedar woodpecker u otros buenos builders de CI (circle, travis, gitlab, etc.).
Tenemos CodeQL aplicado a los PR de OpenZFS. Esto no es un problema para OpenZFS, porque nuestro código no es secreto :)
Aun así, al menos es bueno que se haya resuelto rápido.
¿Esto ya se corrigió?
Este sitio tiene un rendimiento tan malo que casi ni puedo hacer scroll.