Concourse Enumeration & Attacks
Enumeración y Ataques en Concourse
Roles de Usuario y Permisos
Concourse cuenta con cinco roles:
Concourse Admin: Este rol se otorga únicamente 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 en el pipeline como desencadenar construcciones y fijar recursos, sin embargo, no pueden actualizar configuraciones del pipeline.
viewer: Los espectadores del equipo tienen acceso de "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 al respecto en: https://concourse-ci.org/user-roles.html
Ten en cuenta que Concourse agrupa pipelines dentro de Equipos. Por lo tanto, los usuarios pertenecientes a un Equipo podrán gestionar esos pipelines y podrían 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 utilizando la sintaxis ((_source-name_:_secret-path_._secret-field_))
.
Desde la documentación: El nombre de origen 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 campo opcional _secret-field_ especifica un campo en el secreto obtenido para leer. Si se omite, el gestor de credenciales puede optar por leer un 'campo predeterminado' del credencial 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áticos
Las vars estáticas se pueden especificar en pasos de tareas:
O utilizando los siguientes fly
argumentos:
-v
o--var
NOMBRE=VALOR
establece la cadenaVALOR
como el valor para la variableNOMBRE
.-y
o--yaml-var
NOMBRE=VALOR
analizaVALOR
como YAML y lo establece como el valor para la variableNOMBRE
.-i
o--instance-var
NOMBRE=VALOR
analizaVALOR
como YAML y lo establece como el valor para la variable de instanciaNOMBRE
. Consulta Agrupación de Pipelines para obtener más información sobre las variables de instancia.-l
o--load-vars-from
ARCHIVO
cargaARCHIVO
, un documento YAML que contiene nombres de variables y sus valores, y los establece todos.
Gestión de Credenciales
Existen diferentes formas en las que se puede especificar un Gestor de Credenciales 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 filtrar esas credenciales ya que Concourse necesita poder acceder a ellas.
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
.
Enumeración de Inicio de Sesión y Usuario Actual
Para iniciar sesión necesitas conocer el punto final, el nombre del equipo (por defecto es
main
) y un equipo al que pertenece el usuario:fly --target ejemplo login --team-name mi-equipo --concourse-url https://ci.ejemplo.com [--inseguro] [--certificado-cliente=./ruta --clave-cliente=./ruta]
Obtener objetivos configurados:
fly targets
Verificar si la conexión del objetivo configurado sigue siendo válida:
fly -t <objetivo> estado
Obtener el rol del usuario contra el objetivo indicado:
fly -t <objetivo> informacion-usuario
Ten en cuenta que el token de API se guarda en $HOME/.flyrc
de forma predeterminada, si saqueas máquinas podrías encontrar allí las credenciales.
Equipos y Usuarios
Obtener una lista de los Equipos
fly -t <objetivo> equipos
Obtener roles dentro del equipo
fly -t <objetivo> obtener-equipo -n <nombre-equipo>
Obtener una lista de usuarios
fly -t <objetivo> usuarios-activos
Pipelines
Listar pipelines:
fly -t <objetivo> pipelines -a
Obtener el yaml del pipeline (puede haber información sensible en la definición):
fly -t <objetivo> obtener-pipeline -p <nombre-pipeline>
Obtener todas las variables de configuración del pipeline
for nombrepipe in $(fly -t <objetivo> pipelines | grep -Ev "^id" | awk '{print $2}'); do echo $nombrepipe; fly -t <objetivo> obtener-pipeline -p $nombrepipe -j | grep -Eo '"vars":[^}]+'; done
Obtener todos los nombres de secretos de los pipelines utilizados (si puedes crear/modificar un trabajo o secuestrar un contenedor podrías filtrarlos):
Contenedores y Trabajadores
Listar trabajadores:
fly -t <objetivo> workers
Listar contenedores:
fly -t <objetivo> containers
Listar construcciones (para ver qué se está ejecutando):
fly -t <objetivo> builds
Ataques a 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 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 robar.
Sesión dentro de un contenedor en ejecución o ejecutado recientemente
Si tienes suficientes privilegios (rol de miembro o superior) podrás listar pipelines y roles y simplemente obtener una sesión dentro del contenedor <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 metadatos en la nube (desde el pod y desde el nodo, si es posible)
Creación/Modificación de Tuberías
Si tienes suficientes privilegios (rol de miembro o más) podrás crear/modificar nuevas tuberías. Revisa este ejemplo:
Con la modificación/creación de un nuevo pipeline podrás:
Robar los secretos (mediante su impresión o accediendo al contenedor y ejecutando
env
)Escapar al nodo (dándote suficientes privilegios -
privileged: true
)Enumerar/Abusar del endpoint de metadatos de la nube (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 (lo cual probablemente sea mucho más sigiloso):
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 fuga podría ser más "compleja".
En el siguiente PoC vamos a usar el release_agent para escapar con algunas pequeñas modificaciones:
Como habrás notado, esto es simplemente una fuga de release_agent regular modificando el camino del cmd en el nodo
Escapando al nodo desde un contenedor de Worker
Una fuga 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):
Abuso del Servicio Garden - No es un Ataque Real
Estas son solo algunas notas interesantes sobre el servicio, pero debido a que solo está escuchando en localhost, estas notas no tendrá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 maestro web 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:
Solo está expuesto localmente (127.0.0.1) y creo que cuando el worker se autentica nuevamente en la Web con el servicio SSH especial, se crea un túnel para que el servidor web pueda comunicarse 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. Por lo tanto, si deseas 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 vuelve complicado.
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 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, por lo que es como si un contenedor creara un nuevo contenedor dentro de él.
Entrar en un contenedor privilegiado en ejecución
Creación de 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á revisando cada pocos segundos los contenedores que se están ejecutando, y si se descubre uno inesperado, será eliminado. Dado que 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
Última actualización