Posted in

Vulnerabilidades en entornos CI/CD (Parte 3) OWASP Top 10 + CI/CD Goat Moderate y Hard

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 tercera parte cubriremos los siguientes escenarios:

Asimismo, resolveremos 3 de los 11 ejercicios de CI/CD Goat

  • Moderate: Twiddledum y Dodo
  • Hard: Hearts y Mock Turtle

De igual manera como lo hicimos en la entrada anterior, no iremos en el orden de los riesgos definidos por OWASP, sino en orden de complejidad.

Antes de comenzar, te recomendamos ver la entrada anterior para comprender los fundamentos y preparar el entorno vulnerable:

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.


Dependency Chain Abuse – CI/CD Sec 3

Este riesgo está asociado a las fallas de seguridad existentes en la forma en que el entorno de compilación o los productos resultantes, obtienen dependencias de terceros. Tengamos en cuenta que hoy en día, muchas aplicaciones utilizan librerías o paquetes de terceros para que sus funcionalidades sean implementadas. Obtener, gestionar y asegurar estas dependencias no es tarea fácil, sobre todo porque son muchos los componentes que pueden intervenir, desde el gestor de paquetes, hasta las herramientas que se usan para escanearlas.

Referencia: https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-03-Dependency-Chain-Abuse

Para demostrar esta vulnerabilidad, realizaremos el ejercicio Twiddledum, el cual está construido para que utilice el paquete Twiddledee Server (ficticio) como dependencia. El objetivo es obtener la flag6:

  • Dentro del repositorio Wonderland/Twiddledum, podemos observar el archivo package.json, el cual contiene información de la dependencia que necesita:
  • Observamos que la dependencia es obtenida de otro repositorio interno Wonderland/Twiddledee, el cual está bajo nuestro control:
  • Clonaremos el repositorio y modificaremos el archivo principal del paquete: index.js
  • Como podemos ver, hemos añadido una salida de consola, en donde toma el flag de las variables de entorno y la muestra en base64. Confirmamos y subimos los cambios:
  • Ya que no existe ejecución automática del pipeline tras publicar los cambios, debemos ejecutarla manualmente, sin embargo, no se muestra el flag. Y aquí va la explicación del porqué: Los gestores de dependencias, usualmente toman la última versión de un paquete, cuando la versión no ha sido explícitamente indicada. Si pusimos atención en la primera imagen, veremos que la versión del paquete Twiddledee está señalada como «^1.1.0», lo cual indica que debe tomar la versión 1.1.0 o superiores. Como en nuestro caso, no hay ninguna versión superior, entonces no va al repositorio ni la extrae, y por tanto, no se muestra nuestro cambio hecho en index.js. Por tal motivo, generaremos una nueva versión:
  • Y finalmente, obtenemos la respuesta en la consola, tras la ejecución del job:
  • Nos hemos saltado el ejercicio para ir directo al comando que mostrará en consola el flag6. Para llegar a ese punto, primero podemos probar mostrando todas las variables de entorno, y descubriremos que una de ellas es «FLAG6», pero se muestra ofuscada por la propia sanitización que tiene Jenkins.

Insufficient Flow Control Mechanisms

Este riesgo está asociado a la manipulación que pueda existir dentro del flujo de ejecución para poder subir código malicioso o artefactos a través del pipeline, debido a que no existen validaciones o aprobaciones adicionales. Estos son algunos casos que podrían ocurrir:

  • Se envía el código a una rama del repositorio, el cual es desplegado automáticamente a través del pipeline hacia producción.
  • Se envía el código a una rama del repositorio y luego se ejecuta el pipeline manualmente, lo cual permite enviar el código a producción.
  • Se envía el código directamente a una librería de utilidades, que es utilizada por el código que se ejecuta en un sistema de producción.
  • Se abusa de una regla auto-merge en el sistema CI que fusiona automáticamente los codigos tras un PR cuando este cumple un conjunto predefinido de requisitos, enviando así código malicioso no revisado.
  • Se sube un artefacto a un repositorio de artefactos, como un paquete o contenedor, simulando un artefacto legítimo creado por el entorno de compilación. En ese escenario, la falta de controles o verificaciones podría provocar que un pipeline de despliegue lo recoja y lo implemente en producción.
  • Se accede a producción y se cambie directamente el código o la infraestructura de la aplicación (por ejemplo, una función AWS Lambda), sin ninguna aprobación o verificación adicional.

Referencia: https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-01-Insufficient-Flow-Control-Mechanisms

Para demostrar esta vulnerabilidad, realizaremos el ejercicio Dodo, el cual tiene como objetivo capturar el flag7. Veamos el escenario:

  • En este ejercicio, veremos que como parte de las herramientas que usa el pipeline para hacer validaciones, se utiliza Checkov, la cual es una herramienta que permite auditar los códigos IAC (Infraestructura como código) para encontrar potenciales brechas e impedirlas o alertarlas. Para el caso de Dodo, Checkov valida si un bucket S3 de AWS se está creando con permisos de lectura pública, en cuyo caso, lo alerta.
Fuente: https://www.cidersecurity.io/blog/research/malicious-code-analysis-abusing-sast-misconfigurations-to-hack-ci-systems/
  • Entonces, iniciamos explorando el repositorio, en donde veremos el archivo main.tf (Terraform)
  • Clonamos el repositorio, y leemos el archivo
  • Vamos a generar el archivo .checkov.yml apuntando a un directorio que no existe, para poder realizar el bypass
  • Y publicamos los cambios:
  • El bypass se concreta y la consola nos entrega el flag 7:

Otro ejercicio que podemos realizar para comprobar esta vulnerabilidad es Mock-Turtle. El objetivo de este escenario es obtener la flag 10. Veamos como realizarlo:

  • Accedemos al repositorio Wonderland/mock-turtle, y analizamos el archivo Jenkinsfile:
  • En este caso, vemos que hay condiciones para que se apruebe un merge de manera automática:
    • El primer bloque if compara las palabras que se han cambiado. Al parecer, el auto-merge ocurre cuando se actualiza la versión, sin embargo, esto no debería aumentar o disminuir palabras en el resto del contenido, por lo que si la diferencia es 0, se completa la primera verificación.
    • El segundo bloque if, compara que la estructura de la versión sea la misma, es decir X.X.X
    • El tercer bloque ir, compara que justamente el número de versión haya cambiado.
  • Con estas condiciones, podemos realizar un bypass que nos haga pasar las tres verificaciones. Para ello, primero crearemos una rama
  • Ahora, hacemos las siguientes modificaciones. Primero, cambiamos el número de versión, respetando la misma estructura:
  • Luego, iremos al archivo Jenkinsfile para poder colocar el código que nos permitirá obtener el flag 10:
withCredentials([usernamePassword(credentialsId: 'flag10', usernameVariable: 'USERNAME', passwordVariable: 'TOKEN')]) {
    sh 'echo $TOKEN | base64'
}
  • Ahora tenemos que cumplir la primera condición, la cual verifica que la diferencia del recuento de palabras entre el origen y el destino sea 0, para poder lograrlo, vamos a eliminar de un archivo aleatorio, la misma cantidad de palabras que hemos añadido en el Jenkinsfile. Para este ejemplo, modificaremos el archivo README.md
  • Una vez realizada nuestra modificación, crearemos un nuevo PR. Verificamos los cambios
  • Confirmamos el PR y automáticamente se ejecutará un job en Jenkins, veamos la salida:
  • Vemos que las tres condiciones se cumplen, por lo que al ejecutarse nuevamente el pipeline, nos proporcionará la flag:
  • Nota: Si no te funciona en el primer intento, revisa los logs de la ejecución del pipeline, probablemente no pase la primera regla. Si existen diferencias, el proceso no continuará:

Inadequate Identity and Access Management

Este riesgo, tal como su nombre lo indica, está asociado a una gestión inadecuada de identidades y accesos, la cual va a ser aprovechada por un atacante para poder comprometer el ecosistema de CI/CD.

Existen varios vectores escenarios vulnerables para este riesgo:

  • Identidades excesivamente permisivas, contraviniendo el principio de mínimo privilegio
  • Identidades obsoletas, por la falta de un proceso de desaprovisionamiento
  • Identidades locales, por la ausencia de un manejo centralizado de identidades
  • Identidades externas, las cuales impiden que la organización tenga control sobre las mismas
  • Identidades autoregistradas, las cuales por defecto tienen permisos «predeterminados», muchas veces mal configurados

Referencia: https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-02-Inadequate-Identity-And-Access-Management

Para demostrar esta vulnerabilidad, vamos a realizar el ejercicio Hearts, atacando directamente a Jenkins para obtener el flag8. Veamos el escenario:

  • En el panel de Jenkins, vamos a revisar los usuarios existentes. Recordemos que nuestra cuenta es la del usuario Alice. Validamos que además de la cuenta admin, existe un usuario Knave, el cual tiene como rol «Administrador de agentes»:
  • Es decir, este usuario tiene acceso a gestionar los nodos que sirven como agentes para la ejecución de los jobs. Utilizando Hydra (o la herramienta de cracking que quieran), obtenemos su contraseña, la cual es rockme. Ahora podemos loguearnos con nuestras nuevas credenciales
  • Ahora, iremos a la opción de configuración de los nodos y agregaremos uno nuevo:
  • Dentro de las opciones de configuración, vamos a ver que se pueden utilizar las credenciales almacenadas por el sistema, para iniciar la máquina nodo
  • Ahora, ¿cómo podemos conseguir estas credenciales?. Para ello, vamos a utilizar la herramienta ssh-mitm, la cual puedes descargar desde acá: https://github.com/jtesta/ssh-mitm. Esta herramienta, nos permitirá capturar las credenciales durante el intento de autenticación. Seguimos los pasos indicados por el creador de la herramienta para iniciar
  • Lo primero que haremos será crear el contenedor, ejecutando la siguiente sentencia:
docker run --network=cicd-goat_goat -it --rm -v ${PWD}/ssh_mitm_logs:/home/ssh-mitm/log positronsecurity/ssh-mitm
  • Esta sentencia nos permitirá ejecutar el contenedor ssh-mitm en el mismo segmento de red en donde se ha creado el contenedor de Jenkins, como parte del entorno cicd-goat.
  • Obtenemos la IP del contenedor, para ingresarla en la sección de configuración de nuestro nuevo nodo en Jenkins. Es importante también seleccionar «Non veryfing Verification Strategy» en la sección Host Key Verification Strategy y marcar como puerto el 2222, ya que en este está escuchando nuestro contenedor:
  • Una vez que se ha creado el nodo, interceptaremos las credenciales de usuario agent/***, a través de la interceptación en nuestro contenedor ssh-mitm
  • Si vemos la salida de la consola, nos indicará que se está abriendo una conexión a 172.20.0.11:2222, y aunque las SSH Keys no son verificadas, la autenticación es satisfactoria:

Bien, por ahora dejaremos el artículo acá. En la siguiente entrada completaremos el top 10 de OWASP CI/CD. Esperamos que te sea de utilidad.