En esta serie de entradas, repasaremos el top ten de riesgos para entornos de integración continua y despliegue continuo (CI/CD), elaborado por OWASP. En esta segunda parte cubriremos los siguientes escenarios:
- CICD-SEC-6: Insufficient Credential Hygiene
- CICD-SEC-4: Poisoned Pipeline Execution (PPE)
- CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls)
Asimismo, resolveremos 5 de los 11 ejercicios de CI/CD Goat
- Easy: White Rabbit, Mad Hatter y Duchess
- Moderate: Caterpillar y Cheshire Cat
Te estarás preguntando por qué no iremos en orden, y la respuesta es la siguiente: iremos de menos a más, comenzando por los escenarios más fáciles, así nos adentraremos en conceptos que podrían ser más complicados, sobre todo si tenemos poca experiencia con entornos de CI/CD.
Es importante aclarar, que si bien durante el transcurso de la ejecución de los ejercicios, explicaremos algunos conceptos para entender el contexto, no cubriremos todo el detalle teórico, ya que partimos de la premisa que se tienen conocimientos básicos sobre git, docker, jenkins, pipelines, etc.
Antes de comenzar, te recomendamos ver la entrada anterior para comprender los fundamentos y preparar el entorno vulnerable:
https://labitacoradelhacker.com/vulnerabilidades-en-entornos-ci-cd-parte-1-fundamentos
Ahora sí, comencemos:
El contenido teórico de este artículo ha sido tomado en su mayoría de la página de OWASP. Si deseas profundizar en el entendimiento de algún escenario, puedes consultar directamente en los enlaces de referencia.
Insufficient Credential Hygiene – CI/CD Sec 6
Este riesgo está asociada a un incorrecto manejo de credenciales, las cuales muchas veces se harcodean en el código fuente para que puedan ser luego utilizados por las aplicaciones, ya sea para autenticarse contra algún sistema, o durante la ejecución de pipelines al momento de hacer el despliegue de una aplicación. Existen varios escenarios:
- Credenciales contenidas en el código que es subido a una de las ramas en un repositorio
- Credenciales usadas de manera insegura dentro de los procesos de compilación y despliegue
- Credenciales en imágenes de contenedores
- Credenciales que se escriben como salida de la consola (al ejecutar un pipeline)
Referencia: https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-06-Insufficient-Credential-Hygiene
Para demostrar esta vulnerabilidad, ejecutaremos un análisis sobre Duches, uno de los repositorios contenidos en Gitea. Como primer paso, clonaremos el repositorio:

Existen múltiples formas de escanear un repositorio que ha sido publicado, incluso se podría hacer de forma manual, pero tengamos en cuenta que esta forma no es óptima, además de que podríamos dejar escapar algún valor secreto importante.
Para poder analizar un repositorio en profundidad en busca de claves, API Keys, tokens, etc., no solo en el código, sino incluso en los mensajes de commit o en archivos ocultos, utilizaremos la herramienta Gitleaks, la cual puedes descargar de aquí: https://github.com/gitleaks/gitleaks
Puedes instalarla directamente en tu sistema, o usarla en forma de contenedor:
docker pull zricethezav/gitleaks:latest
docker run -v ${PWD}/duchess:/path zricethezav/gitleaks:latest detect --source="path" --verbose


Al ejecutar el contenedor, se muestran llaves privadas y tokens Pypi.
Esta acción podemos ejecutarla sobre nuestros propios repositorios, para asegurarnos que no estamos exponiendo valores sensibles, sobre todo si hablamos de repositorios que serán públicos.
Poisoned Pipeline Execution (PPE) – CI/CD Sec 4
Primero entendamos que es un Pipeline: Un pipeline DevOps es una serie de pasos automatizados que permiten construir, probar y desplegar aplicaciones de manera eficiente y continua. Es una herramienta clave dentro de la metodología DevOps, diseñada para integrar y automatizar procesos en el ciclo de vida del desarrollo de software.
Los pipelines son construidos en aplicaciones de integración tales como Jenkins, Github actions, Bamboo, Gitlab CI, Azure DevOps, Codeship, etc., generalmente usando lenguajes específicos como Yaml o Groovy, incorporando también lenguajes de scripting, cuando se necesita lanzar algún comando.
Entonces, ¿en que consiste el ataque de envenenamiento de la ejecución de un pipeline?
Este ataque consiste en manipular el proceso de compilación inyectando comandos o código malicioso durante la ejecución del pipeline. Esto es posible cuando el atacante tiene acceso o ha comprometido el repositorio donde se aloja el código (por ejemplo, Gitea, Github, Azure Repos, etc) pero no directamente al entorno de compilación. La vulnerabilidad se presenta en que muchos pipeline se ejecutan a partir de la lectura de archivos de configuración u otros archivos que existen dentro del repositorio, o incluso, en algunos casos, son ejecutados directamente cuando se ejecuta un pull request o commits, por lo que no se ejecuta una revisión del código del pipeline.
Existen 2 tipos principales de PPE:
1) PPE Directo: En este escenario, el atacante modifica directamente el archivo de configuración que reside en el repositorio, para que ante alguna acción como un PR o push, se ejecuten los comandos o acciones inyectadas.
Por ejemplo, supongamos que un pipeline se ejecuta en Github actions, cuando se realiza una modificación sobre algún archivo en una rama remota en el repositorio. El atacante crea una nueva rama remota actualizando el archivo yaml con las instrucciones, permitiendo ejecutar comandos que permitan robar credenciales de acceso:

2) PPE Indirecto: Este ataque se produce cuando no se tiene acceso directo sobre el archivo de configuración, ya sea porque este se encuentra en una rama privada o se almacena un lugar exclusivo separado del código fuente, o cuando la compilación se define en la propia aplicación, y no en un archivo almacenado en el código fuente. Sin embargo, tengamos en cuenta que muchas veces los pipeline ejecutan en sus instrucciones comandos que necesitan otros tipos de archivo, por ejemplo, cuando se hace «make» apuntando a un archivo Makefile.
Por ejemplo, tomemos como base el escenario anterior, y supongamos que aunque el atacante puede leer el archivo de configuración, el pipeline se ejecuta tomando el archivo de una rama protegida, por lo que aunque este se modifique, no afectará el proceso. Parecería que el ataque no podría ejecutarse, sin embargo, el archivo de configuración lleva las siguientes instrucciones:
stage('build') {
steps {
withAWS(credentials: 'AWS_key', region: 'us-east-1') {
sh 'make build'
sh 'make clean'
}
}
}
y en el Makefile, se encuentran las instrucciones para build y clean:
build:
echo "building…"
clean:
echo "cleaning…"
En este caso, alterando el archivo Makefile, el atacante logra obtener las credenciales de acceso:

De acuerdo a OWASP, existe otro tipo de ataque de envenenamiento, el cual se ejecuta sobre repositorios públicos, que usualmente aceptan modificaciones por parte de usuarios que «contribuyen» con el proyecto, en donde existen también sistemas CI que prueban el nuevo código tras una aprobación de un PR. Si existen ejecución de pipelines de manera automática (sin verificación), entonces es posible conseguir inyectar código malicioso. Sin embargo, este tercer tipo de ataque, necesariamente tiene que utilizar alguna de las dos técnicas descritas anteriormente, ya sea porque se edite de manera directa el archivo de configuración o se realice de manera indirecta.
Referencia: https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-04-Poisoned-Pipeline-Execution
Ahora, veamos como ejecutar estos ataques en nuestro entorno preparado:
PPE Directo
Partiremos de la premisa que el atacante tiene acceso al repositorio de código, que en este caso es Gitea. El ejercicio es White-Rabbit y el objetivo es obtener la flag1. Veamos como conseguirlo:
- En el repositorio Wonderland/white-rabbit, tenemos acceso al Jenkinsfile

- Dentro de las etapas de la ejecución, encontraremos que uno de los pasos es instalar requerimientos, los cuales vienen acompañados de un código bash para ejecutar pip3 e instalar los paquetes necesarios:

- Clonaremos el repositorio, y crearemos una nueva rama, a la cual llamaremos «challenge1»

- Y modificamos el archivo Jenkinsfile, para que muestre el flag1. Para este caso ejemplo, solo haremos un echo, pero tengamos en cuenta que podríamos tranquilamente enviar el valor a un servidor remoto.

- Confirmamos y subimos los cambios, para luego crear un nuevo Pull Request que fusione nuestros cambios con la rama principal:



- En este caso, el sistema de integración ejecuta el pipeline de manera automática cuando un PR es lanzado, entregándonos el flag1


- Adicional: Puedes decodificar el valor base64 y añadirlo al panel. Este paso será omitido en los próximos ataques

Ahora, veamos otro ejemplo, desde la perspectiva de un atacante que tiene acceso a un repositorio público, y en el cual va a «colaborar». El ejercicio es Caterpillar y el objetivo es obtener la flag 2:
- Accedemos al repositorio público Wonderland/caterpillar:

- Crearemos un fork del repositorio. Un fork es una copia independiente que nos permite trabajar en el desarrollo de un producto sin afectar al código original.

- Ahora ya tenemos el repositorio fork en thealice/caterpillar

- En este caso, aunque modificamos el Jenkinsfile para que nos muestre el flag2 con un echo, no lograremos mostrarlo. Esto sucede porque al recibir el PR, el job que se ejecuta es Wonderland-Caterpillar-Test, el cual no encuentra el flag2. Podemos suponer que esto se hace para auditar previamente los cambios. Para poder completar nuestro ataque, imprimiremos (o enviaremos a un servidor remoto) las variables de entorno, ya que durante la ejecución del pipeline en el entorno test, nos muestra el siguiente error:


- Hacemos un PR para fusionar la rama main de Wonderland/caterpillar, desde thealice/caterpillar

- Ahora lo que se entrega es el token GITEA_TOKEN, el cual es un token de acceso personal. Usualmente son usados con fines de lectura, pero en este caso, aprovecharemos una configuración inadecuada que nos permitirá escribir sobre el repo.

- Con este token, clonaremos el repositorio e intentaremos subir cambios. Esta vez si escribiremos el echo para flag2:


- Al hacer el commit, se ejecutará el job en Wonderland-Caterpillar-Prod, ahora sí, mostrandonos el flag2


PPE Indirecto
Nuevamente partiremos de la premisa que el atacante tiene acceso al repositorio de código, que en este caso es Gitea. El ejercicio es Mad Hatter y el objetivo es obtener la flag3. Veamos como conseguirlo:
- En este caso, aunque podemos acceder al repositorio, veremos que no tenemos acceso al Jenkinsfile

- Esto es porque el Jenkinsfile está en un repositorio diferente, al cual obviamente no vamos a poder modificar. Intentarlo solo nos hará perder tiempo.

- Si revisamos el contenido del archivo, nos daremos cuenta que en uno de los pasos, ejecuta la instrucción sh ´make || true´.

- Si pusimos atención en la primera imagen, veremos un archivo Makefile al cual si podemos modificar

- Añadiremos el comando echo ${FLAG} | base64, y una vez que hagamos commit sobre los cambios, la salida de la consola para el job ejecutado, nos dará la flag 3

Insufficient PBAC (Pipeline-Based Access Controls) – CI/CD Sec 5
Para poder entender este riesgo, es necesario primero entender que los sistemas CI, están basados en componentes tecnológicos que permiten la compilación. Muchas veces estos se basan en nodos que tienen acceso a recursos locales o incluso a recursos fuera del entorno de ejecución. Cuando un atacante puede aprovechar los defectos de seguridad en el otorgamiento de esos accesos, para conseguir extraer información o desplazarse lateralmente a otros recursos.
Referencia: https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-05-Insufficient-PBAC
Para demostrar esta vulnerabilidad, realizaremos el ejercicio Cheshire Cat, el cual será un ataque combinado, aprovechando el conocimiento de como ejecutar un PPE directo, para luego aprovechar el inadecuado manejo de accesos al nodo local de compilación.
- Accederemos al repositorio Wonderland/cheshire-cat

- Clonaremos el repo, y luego crearemos la rama «challenge5»

- Ahora, editaremos el archivo Jenkinsfile, haciendo los siguientes cambios
agent any ---- agent {label 'built-in'} # Para que no utilice cualquier agente disponible, sino el local, el cual por defecto tiene la etiqueta built-in
sh 'cat ~/flag5.txt' # Modificar o añadir sobre el stage de Install_Requeriments

- Ahora sí, confirmamos los cambios y los subimos

- Creamos el PR

- El job nos entrega la flag 5

Por ahora, dejaremos el artículo acá. En el siguiente continuaremos abarcando el OWASP Top 10 CI/CD. Esperamos que te haya sido de utilidad.