Concourse Enumeration & Attacks
Concourse Enumeración y Ataques
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 por defecto 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 activar construcciones y fijar recursos, sin embargo no pueden actualizar configuraciones de 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 pertenecen 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 Gestor de Credenciales
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 gestor de credenciales a nivel de clúster, o el valor puede ser proporcionado estáticamente.
El _secret-field opcional_ especifica un campo en el secreto obtenido para leer. Si se omite, el gestor de credenciales puede optar por leer un 'campo por defecto' del secreto obtenido si el campo existe.
Además, el secret-path y 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
.
Vars Estáticas
Las vars estáticas pueden ser especificadas en pasos de tareas:
Or usando los siguientes fly
argumentos:
-v
o--var
NAME=VALUE
establece la cadenaVALUE
como el valor para la varNAME
.-y
o--yaml-var
NAME=VALUE
analizaVALUE
como YAML y lo establece como el valor para la varNAME
.-i
o--instance-var
NAME=VALUE
analizaVALUE
como YAML y lo establece como el valor para la var de instanciaNAME
. Consulta Agrupación de Pipelines para aprender más sobre las vars de instancia.-l
o--load-vars-from
FILE
cargaFILE
, un documento YAML que contiene la asignación de nombres de var a valores, y los establece todos.
Gestión de Credenciales
Hay diferentes formas en que un Gestor de Credenciales puede ser especificado en un pipeline, lee cómo en https://concourse-ci.org/creds.html. Además, Concourse admite 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 de usuario actual
Para iniciar sesión necesitas conocer el endpoint, el nombre del equipo (el predeterminado 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 targets configurados:
fly targets
Ver si la conexión de target configurada sigue siendo válida:
fly -t <target> status
Obtener el rol del usuario contra el target indicado:
fly -t <target> userinfo
Ten en cuenta que el token de API se guarda en $HOME/.flyrc
por defecto, si estás saqueando 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 vars 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 de pipelines utilizados (si puedes crear/modificar un trabajo o secuestrar un contenedor podrías exfiltrarlos):
Contenedores y Trabajadores
Listar trabajadores:
fly -t <target> workers
Listar contenedores:
fly -t <target> containers
Listar construcciones (para ver qué está en ejecución):
fly -t <target> builds
Ataques de Concourse
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 y vars de los secretos utilizados por el pipeline. Las vars 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, es posible que puedas:
Robar los secretos dentro del contenedor
Intentar escapar al nodo
Enumerar/Abusar del punto final de metadatos de la nube (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. Consulta este ejemplo:
Con la modificación/creación de un nuevo pipeline podrás:
Robar los secretos (a través de su impresión o accediendo al contenedor y ejecutando
env
)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 nuevo pipeline completo, puedes simplemente ejecutar una tarea personalizada (que probablemente será mucho más sigilosa):
Escapando al nodo desde una tarea privilegiada
En las secciones anteriores vimos cómo ejecutar una tarea privilegiada con concourse. Esto no le dará al contenedor exactamente el mismo acceso que la bandera privilegiada en un contenedor de docker. Por ejemplo, no verá el dispositivo del sistema de archivos del nodo en /dev, por lo que la escapatoria podría ser más "compleja".
En la siguiente PoC vamos a usar el release_agent para escapar con algunas pequeñas modificaciones:
Como habrás notado, esto es solo un escape de release_agent regular solo modificando la ruta del cmd en el nodo
Escapando al nodo desde un contenedor Worker
Un escape de release_agent regular con una modificación menor es suficiente para esto:
Escapando al nodo desde el contenedor web
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:
Puedes usar esas credenciales para iniciar sesión en 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 utiliza concourse (dirección, nombre de usuario, contraseña 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 dado que solo está escuchando en localhost, estas notas no presentarán ningún impacto que no hayamos explotado antes.
Por defecto, cada trabajador de concourse estará ejecutando un Garden en el puerto 7777. Este servicio es utilizado por el maestro web para indicar al trabajador lo que necesita ejecutar (descargar la imagen y ejecutar cada tarea). Esto suena bastante bien para un atacante, pero hay algunas buenas protecciones:
Está expuesto localmente (127..0.0.1) y creo que cuando el trabajador se autentica contra la 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 trabajador.
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 trabajadores 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, así que si podemos ejecutar comandos en un contenedor privilegiado creado por el trabajador actual, podríamos escapar al nodo.
Ten en cuenta que al jugar con concourse noté que cuando se genera un nuevo contenedor para ejecutar algo, los procesos del contenedor son accesibles desde el contenedor del trabajador, así 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 (solo ejecuta un UID aleatorio) y ejecutar algo en él:
Sin embargo, el servidor web está verificando cada pocos segundos los contenedores que se están ejecutando, y si se descubre uno inesperado, será eliminado. Como la comunicación se realiza 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