Concourse Enumeration & Attacks
Concourse Enumeration & Attacks
Roles de Usuario y Permisos
Concourse viene con cinco roles:
Concourse Admin: Este rol solo se otorga a los propietarios del equipo principal (equipo inicial predeterminado de concourse). Los administradores pueden configurar otros equipos (por ejemplo:
fly set-team
,fly destroy-team
...). Los permisos de este rol no pueden ser afectados por RBAC.owner: Los propietarios del equipo pueden modificar todo dentro del equipo.
member: Los miembros del equipo pueden leer y escribir dentro de los activos del equipo pero no pueden modificar la configuración del equipo.
pipeline-operator: Los operadores de pipeline pueden realizar operaciones de pipeline como desencadenar compilaciones y fijar recursos, sin embargo, no pueden actualizar las configuraciones del pipeline.
viewer: Los espectadores del equipo tienen acceso "solo lectura" a un equipo y sus pipelines.
Además, los permisos de los roles owner, member, pipeline-operator y viewer pueden ser modificados configurando RBAC (configurando más específicamente sus acciones). Lee más sobre esto en: https://concourse-ci.org/user-roles.html
Ten en cuenta que Concourse agrupa pipelines dentro de Equipos. Por lo tanto, los usuarios que pertenezcan a un Equipo podrán gestionar esos pipelines y pueden existir varios Equipos. Un usuario puede pertenecer a varios Equipos y tener diferentes permisos dentro de cada uno de ellos.
Vars y Credential Manager
En las configuraciones YAML puedes configurar valores usando la sintaxis ((_source-name_:_secret-path_._secret-field_))
.
De la documentación: El source-name es opcional, y si se omite, se utilizará el cluster-wide credential manager, o el valor puede ser proporcionado estáticamente.
El opcional _secret-field_ especifica un campo en el secreto recuperado para leer. Si se omite, el credential manager puede optar por leer un 'campo predeterminado' del credencial recuperado si el campo existe.
Además, el secret-path y el secret-field pueden estar rodeados por comillas dobles "..."
si contienen caracteres especiales como .
y :
. Por ejemplo, ((source:"my.secret"."field:1"))
establecerá el secret-path en my.secret
y el secret-field en field:1
.
Static Vars
Las static vars pueden especificarse en pasos de tareas:
O usando los siguientes argumentos de fly
:
-v
o--var
NAME=VALUE
establece la cadenaVALUE
como el valor para la variableNAME
.-y
o--yaml-var
NAME=VALUE
analizaVALUE
como YAML y lo establece como el valor para la variableNAME
.-i
o--instance-var
NAME=VALUE
analizaVALUE
como YAML y lo establece como el valor para la variable de instanciaNAME
. Ver Grouping Pipelines para aprender más sobre las variables de instancia.-l
o--load-vars-from
FILE
cargaFILE
, un documento YAML que contiene nombres de variables mapeados a valores, y los establece todos.
Gestión de Credenciales
Hay diferentes maneras en que un Gestor de Credenciales puede ser especificado en una pipeline, lee cómo en https://concourse-ci.org/creds.html. Además, Concourse soporta diferentes gestores de credenciales:
Ten en cuenta que si tienes algún tipo de acceso de escritura a Concourse puedes crear trabajos para exfiltrar esos secretos ya que Concourse necesita poder acceder a ellos.
Enumeración de Concourse
Para enumerar un entorno de concourse primero necesitas reunir credenciales válidas o encontrar un token autenticado probablemente en un archivo de configuración .flyrc
.
Inicio de sesión y enumeración del usuario actual
Para iniciar sesión necesitas conocer el endpoint, el nombre del equipo (por defecto es
main
) y un equipo al que pertenece el usuario:fly --target example login --team-name my-team --concourse-url https://ci.example.com [--insecure] [--client-cert=./path --client-key=./path]
Obtener objetivos configurados:
fly targets
Verificar si la conexión al objetivo configurado sigue siendo válida:
fly -t <target> status
Obtener el rol del usuario contra el objetivo indicado:
fly -t <target> userinfo
Ten en cuenta que el token de API se guarda en $HOME/.flyrc
por defecto, al saquear una máquina podrías encontrar allí las credenciales.
Equipos y Usuarios
Obtener una lista de los Equipos
fly -t <target> teams
Obtener roles dentro del equipo
fly -t <target> get-team -n <team-name>
Obtener una lista de usuarios
fly -t <target> active-users
Pipelines
Listar pipelines:
fly -t <target> pipelines -a
Obtener yaml de pipeline (información sensible podría encontrarse en la definición):
fly -t <target> get-pipeline -p <pipeline-name>
Obtener todas las variables declaradas en la configuración del pipeline
for pipename in $(fly -t <target> pipelines | grep -Ev "^id" | awk '{print $2}'); do echo $pipename; fly -t <target> get-pipeline -p $pipename -j | grep -Eo '"vars":[^}]+'; done
Obtener todos los nombres de secretos usados en los pipelines (si puedes crear/modificar un trabajo o secuestrar un contenedor podrías exfiltrarlos):
Containers & Workers
Listar workers:
fly -t <target> workers
Listar containers:
fly -t <target> containers
Listar builds (para ver qué está en ejecución):
fly -t <target> builds
Concourse Attacks
Fuerza bruta de credenciales
admin:admin
test:test
Enumeración de secretos y parámetros
En la sección anterior vimos cómo puedes obtener todos los nombres de secretos y variables utilizados por el pipeline. Las variables pueden contener información sensible y el nombre de los secretos será útil más adelante para intentar robarlos.
Sesión dentro de un contenedor en ejecución o recientemente ejecutado
Si tienes suficientes privilegios (rol de miembro o más) podrás listar pipelines y roles y simplemente obtener una sesión dentro del contenedor de <pipeline>/<job>
usando:
Con estos permisos podrías ser capaz de:
Robar los secretos dentro del contenedor
Intentar escapar al nodo
Enumerar/Abusar del endpoint de metadata de cloud (desde el pod y desde el nodo, si es posible)
Creación/Modificación de Pipeline
Si tienes suficientes privilegios (rol de miembro o más) podrás crear/modificar nuevos pipelines. Revisa este ejemplo:
Con la modificación/creación de un nuevo pipeline podrás:
Robar los secretos (mostrándolos con
echo
o entrando en el contenedor y ejecutandoenv
)Escapar al nodo (dándote suficientes privilegios -
privileged: true
)Enumerar/Abusar del endpoint de cloud metadata (desde el pod y desde el nodo)
Eliminar el pipeline creado
Ejecutar Tarea Personalizada
Esto es similar al método anterior, pero en lugar de modificar/crear un pipeline completo, puedes simplemente ejecutar una tarea personalizada (lo cual probablemente será mucho más sigiloso):
Escaping to the node from privileged task
En las secciones anteriores vimos cómo ejecutar una tarea privilegiada con concourse. Esto no dará al contenedor exactamente el mismo acceso que la bandera privilegiada en un contenedor docker. Por ejemplo, no verás el dispositivo del sistema de archivos del nodo en /dev, por lo que el escape podría ser más "complejo".
En el siguiente PoC vamos a usar el release_agent para escapar con algunas pequeñas modificaciones:
Como habrás notado, esto es solo un release_agent escape regular solo modificando la ruta del cmd en el nodo.
Escapando al nodo desde un contenedor Worker
Un release_agent escape regular con una modificación menor es suficiente para esto:
Escaping to the node from the Web container
Incluso si el contenedor web tiene algunas defensas deshabilitadas, no se está ejecutando como un contenedor privilegiado común (por ejemplo, no puedes montar y las capacidades son muy limitadas, por lo que todas las formas fáciles de escapar del contenedor son inútiles).
Sin embargo, almacena credenciales locales en texto claro:
Podrías usar esas credenciales para iniciar sesión contra el servidor web y crear un contenedor privilegiado y escapar al nodo.
En el entorno también puedes encontrar información para acceder a la instancia de postgresql que usa concourse (dirección, username, password y base de datos, entre otra información):
Abusando del Servicio Garden - No es un Ataque Real
Estas son solo algunas notas interesantes sobre el servicio, pero como solo escucha en localhost, estas notas no presentarán ningún impacto que no hayamos explotado antes.
Por defecto, cada worker de concourse ejecutará un servicio Garden en el puerto 7777. Este servicio es utilizado por el Web master para indicar al worker qué necesita ejecutar (descargar la imagen y ejecutar cada tarea). Esto suena bastante bien para un atacante, pero hay algunas protecciones interesantes:
Está expuesto solo localmente (127.0.0.1) y creo que cuando el worker se autentica contra el Web con el servicio SSH especial, se crea un túnel para que el servidor web pueda hablar con cada servicio Garden dentro de cada worker.
El servidor web está monitoreando los contenedores en ejecución cada pocos segundos, y los contenedores inesperados son eliminados. Así que si quieres ejecutar un contenedor personalizado necesitas manipular la comunicación entre el servidor web y el servicio garden.
Los workers de Concourse se ejecutan con altos privilegios de contenedor:
Sin embargo, técnicas como montar el dispositivo /dev del nodo o release_agent no funcionarán (ya que el dispositivo real con el sistema de archivos del nodo no es accesible, solo uno virtual). No podemos acceder a los procesos del nodo, por lo que escapar del nodo sin exploits de kernel se complica.
En la sección anterior vimos cómo escapar de un contenedor privilegiado, por lo que si podemos ejecutar comandos en un contenedor privilegiado creado por el trabajador actual, podríamos escapar al nodo.
Tenga en cuenta que jugando con concourse noté que cuando se genera un nuevo contenedor para ejecutar algo, los procesos del contenedor son accesibles desde el contenedor del trabajador, por lo que es como un contenedor creando un nuevo contenedor dentro de él.
Entrando en un contenedor privilegiado en ejecución
Creando un nuevo contenedor privilegiado
Puedes crear muy fácilmente un nuevo contenedor (simplemente ejecuta un UID aleatorio) y ejecutar algo en él:
Sin embargo, el servidor web está verificando cada pocos segundos los contenedores que están en ejecución, y si se descubre uno inesperado, será eliminado. Como la comunicación se está realizando en HTTP, podrías manipular la comunicación para evitar la eliminación de contenedores inesperados:
Referencias
https://concourse-ci.org/vars.html
Last updated